mirror of
https://github.com/mCaptcha/cache.git
synced 2024-11-24 09:25:30 +03:00
bump redismodules to v0.21
This commit is contained in:
parent
ef5e374109
commit
a0fefa5649
7 changed files with 102 additions and 47 deletions
65
Cargo.lock
generated
65
Cargo.lock
generated
|
@ -68,9 +68,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.68"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
|
||||
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
|
@ -187,13 +187,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.14"
|
||||
version = "0.99.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc7b9cef1e351660e5443924e4f43ab25fbbed3e9a5f052df3677deb4d6b320"
|
||||
checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"rustc_version",
|
||||
"syn 1.0.73",
|
||||
]
|
||||
|
||||
|
@ -294,9 +295,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.97"
|
||||
version = "0.2.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
|
||||
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
|
@ -311,7 +312,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "libmcaptcha"
|
||||
version = "0.1.4"
|
||||
source = "git+https://github.com/mCaptcha/libmcaptcha?branch=master#60107a2b6f5b1b80c7351a42e05c8c0c6cdbf5de"
|
||||
source = "git+https://github.com/mCaptcha/libmcaptcha?branch=master#0d8de9092b68fcdec74c7bee9e037aeda29c7a87"
|
||||
dependencies = [
|
||||
"derive_builder",
|
||||
"derive_more",
|
||||
|
@ -383,6 +384,15 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||
dependencies = [
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.7"
|
||||
|
@ -461,9 +471,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redis-module"
|
||||
version = "0.18.0"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7977889a981324b3fdd56b2cc366d315639d34d264f8a750b2d75a23b4bd78fd"
|
||||
checksum = "8b9ca0e81e4f57537140e45fe0723264a4f4b1f13beb6fffa313ea67783c01e3"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"bitflags",
|
||||
|
@ -497,12 +507,39 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
|
||||
dependencies = [
|
||||
"pest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.126"
|
||||
|
@ -615,14 +652,20 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.8.0"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "570c2eb13b3ab38208130eccd41be92520388791207fde783bda7c1e8ace28d4"
|
||||
checksum = "98c8b05dc14c75ea83d63dd391100353789f5f24b8b3866542a5e85c8be8e985"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.8.0"
|
||||
|
|
|
@ -13,7 +13,7 @@ crate-type = ["cdylib"]
|
|||
name = "cache"
|
||||
|
||||
[dependencies]
|
||||
redis-module = { version="0.18", features = ["experimental-api"]}
|
||||
redis-module = { version="0.21", features = ["experimental-api"]}
|
||||
libc = "0.2"
|
||||
serde_json = "1.0.64"
|
||||
serde = {version = "1.0.126", features = ["derive"]}
|
||||
|
|
|
@ -22,9 +22,8 @@ use std::time::Duration;
|
|||
use redis_module::key::RedisKeyWritable;
|
||||
use redis_module::native_types::RedisType;
|
||||
use redis_module::raw::KeyType;
|
||||
use redis_module::NotifyEvent;
|
||||
//use redis_module::RedisError;
|
||||
use redis_module::{raw, Context};
|
||||
use redis_module::{NotifyEvent, RedisString};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::errors::*;
|
||||
|
@ -79,7 +78,7 @@ impl Bucket {
|
|||
|
||||
let bucket_name = bucket_name.unwrap();
|
||||
|
||||
let bucket = ctx.open_key_writable(&bucket_name);
|
||||
let bucket = ctx.open_key_writable(&RedisString::create(ctx.ctx, &bucket_name));
|
||||
if bucket.key_type() == KeyType::Empty {
|
||||
ctx.log_debug(&format!("Bucket doesn't exist: {}", &key_name));
|
||||
} else {
|
||||
|
@ -119,7 +118,8 @@ impl Bucket {
|
|||
"reading captcha: {} with decr count {}",
|
||||
&captcha, count
|
||||
));
|
||||
let stored_captcha = ctx.open_key_writable(&captcha);
|
||||
let stored_captcha =
|
||||
ctx.open_key_writable(&RedisString::create(ctx.ctx, &captcha));
|
||||
if stored_captcha.key_type() == KeyType::Empty {
|
||||
continue;
|
||||
}
|
||||
|
@ -139,19 +139,25 @@ impl Bucket {
|
|||
// get bucket
|
||||
let bucket_name = get_bucket_name(bucket_instant);
|
||||
|
||||
let timer = ctx.open_key_writable(&get_timer_name_from_bucket_name(&bucket_name));
|
||||
let timer = ctx.open_key_writable(&RedisString::create(
|
||||
ctx.ctx,
|
||||
&get_timer_name_from_bucket_name(&bucket_name),
|
||||
));
|
||||
let _ = timer.delete();
|
||||
|
||||
ctx.log_debug(&format!("Bucket instant: {}", &bucket_instant));
|
||||
|
||||
let bucket = ctx.open_key_writable(&bucket_name);
|
||||
let bucket = ctx.open_key_writable(&RedisString::create(ctx.ctx, &bucket_name));
|
||||
Bucket::decrement_runner(ctx, &bucket);
|
||||
|
||||
if let Err(e) = bucket.delete() {
|
||||
ctx.log_warning(&format!("enountered error while deleting hashmap: {:?}", e));
|
||||
}
|
||||
|
||||
let timer = ctx.open_key_writable(&get_timer_name_from_bucket_name(&bucket_name));
|
||||
let timer = ctx.open_key_writable(&RedisString::create(
|
||||
ctx.ctx,
|
||||
&get_timer_name_from_bucket_name(&bucket_name),
|
||||
));
|
||||
if let Err(e) = timer.delete() {
|
||||
ctx.log_warning(&format!(
|
||||
"enountered error while deleting bucket tiemr: {:?}",
|
||||
|
@ -163,10 +169,10 @@ impl Bucket {
|
|||
/// increments count of key = captcha and registers for auto decrement
|
||||
#[inline]
|
||||
fn increment(ctx: &Context, captcha: &str) -> CacheResult<String> {
|
||||
let captcha_name = get_captcha_key(captcha);
|
||||
ctx.log_debug(&captcha_name);
|
||||
let captcha_name = get_captcha_key(&captcha);
|
||||
// ctx.log_debug(&captcha_name);
|
||||
// increment
|
||||
let captcha = ctx.open_key_writable(&captcha_name);
|
||||
let captcha = ctx.open_key_writable(&RedisString::create(ctx.ctx, &captcha_name));
|
||||
ctx.log_debug("loading mcaptcha");
|
||||
let captcha = MCaptcha::get_mut_mcaptcha(&captcha)?;
|
||||
|
||||
|
@ -200,10 +206,10 @@ impl Bucket {
|
|||
let bucket_instant = get_bucket_instant(duration)?;
|
||||
let bucket_name = get_bucket_name(bucket_instant);
|
||||
|
||||
ctx.log_debug(&format!("Bucket name: {}", &bucket_name));
|
||||
// ctx.log_debug(&format!("Bucket name: {}", &bucket_name));
|
||||
|
||||
// get bucket
|
||||
let bucket = ctx.open_key_writable(&bucket_name);
|
||||
let bucket = ctx.open_key_writable(&RedisString::create(ctx.ctx, &bucket_name));
|
||||
|
||||
match bucket.get_value::<Bucket>(&MCAPTCHA_BUCKET_TYPE)? {
|
||||
Some(bucket) => match bucket.decrement.get_mut(&captcha_name) {
|
||||
|
@ -217,7 +223,10 @@ impl Bucket {
|
|||
let mut counter = Bucket::new(ctx, duration)?;
|
||||
counter.decrement.insert(captcha_name, 1);
|
||||
bucket.set_value(&MCAPTCHA_BUCKET_TYPE, counter)?;
|
||||
let timer = ctx.open_key_writable(&get_timer_name_from_bucket_name(&bucket_name));
|
||||
let timer = ctx.open_key_writable(&RedisString::create(
|
||||
ctx.ctx,
|
||||
&get_timer_name_from_bucket_name(&bucket_name),
|
||||
));
|
||||
timer.write("1")?;
|
||||
timer.set_expire(Duration::from_secs(duration + BUCKET_EXPIRY_OFFSET))?;
|
||||
}
|
||||
|
@ -227,12 +236,12 @@ impl Bucket {
|
|||
}
|
||||
|
||||
/// Create new counter
|
||||
pub fn counter_create(ctx: &Context, args: Vec<String>) -> RedisResult {
|
||||
pub fn counter_create(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
|
||||
let mut args = args.into_iter().skip(1);
|
||||
// mcaptcha captcha key name
|
||||
let key_name = args.next_string()?;
|
||||
// expiry
|
||||
let res = bucket::Bucket::increment(ctx, &key_name)?;
|
||||
let res = Self::increment(ctx, &key_name)?;
|
||||
Ok(res.into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ use redis_module::native_types::RedisType;
|
|||
use redis_module::raw::KeyType;
|
||||
use redis_module::NextArg;
|
||||
use redis_module::RedisResult;
|
||||
use redis_module::RedisString;
|
||||
use redis_module::REDIS_OK;
|
||||
use redis_module::{raw, Context};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -43,7 +44,7 @@ impl Challenge {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn create_challenge(ctx: &Context, args: Vec<String>) -> RedisResult {
|
||||
pub fn create_challenge(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
|
||||
let mut args = args.into_iter().skip(1);
|
||||
let captcha = args.next_string()?;
|
||||
let json = args.next_string()?;
|
||||
|
@ -51,7 +52,7 @@ impl Challenge {
|
|||
|
||||
let challenge_name = get_challenge_name(&captcha, &add_challenge.challenge);
|
||||
|
||||
let key = ctx.open_key_writable(&challenge_name);
|
||||
let key = ctx.open_key_writable(&RedisString::create(ctx.ctx, &challenge_name));
|
||||
if key.key_type() != KeyType::Empty {
|
||||
return Err(CacheError::DuplicateChallenge.into());
|
||||
}
|
||||
|
@ -63,14 +64,14 @@ impl Challenge {
|
|||
REDIS_OK
|
||||
}
|
||||
|
||||
pub fn delete_challenge(ctx: &Context, args: Vec<String>) -> RedisResult {
|
||||
pub fn delete_challenge(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
|
||||
let mut args = args.into_iter().skip(1);
|
||||
let captcha = args.next_string()?;
|
||||
let challenge = args.next_string()?;
|
||||
|
||||
let challenge_name = get_challenge_name(&captcha, &challenge);
|
||||
|
||||
let key = ctx.open_key_writable(&challenge_name);
|
||||
let key = ctx.open_key_writable(&RedisString::create(ctx.ctx, &challenge_name));
|
||||
if key.key_type() == KeyType::Empty {
|
||||
Err(CacheError::ChallengeNotFound.into())
|
||||
} else {
|
||||
|
@ -79,14 +80,14 @@ impl Challenge {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_challenge(ctx: &Context, args: Vec<String>) -> RedisResult {
|
||||
pub fn get_challenge(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
|
||||
let mut args = args.into_iter().skip(1);
|
||||
let captcha = args.next_string()?;
|
||||
let challenge = args.next_string()?;
|
||||
|
||||
let challenge_name = get_challenge_name(&captcha, &challenge);
|
||||
|
||||
let key = ctx.open_key_writable(&challenge_name);
|
||||
let key = ctx.open_key_writable(&RedisString::create(ctx.ctx, &challenge_name));
|
||||
if key.key_type() == KeyType::Empty {
|
||||
return Err(CacheError::ChallengeNotFound.into());
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ use redis_module::key::RedisKeyWritable;
|
|||
use redis_module::native_types::RedisType;
|
||||
use redis_module::raw::KeyType;
|
||||
use redis_module::RedisError;
|
||||
use redis_module::RedisString;
|
||||
use redis_module::RedisValue;
|
||||
use redis_module::{Context, RedisResult};
|
||||
use redis_module::{NextArg, REDIS_OK};
|
||||
|
@ -116,12 +117,12 @@ impl MCaptcha {
|
|||
}
|
||||
|
||||
/// Get counter value
|
||||
pub fn get_count(ctx: &Context, args: Vec<String>) -> RedisResult {
|
||||
pub fn get_count(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
|
||||
let mut args = args.into_iter().skip(1);
|
||||
let key_name = args.next_string()?;
|
||||
let key_name = get_captcha_key(&key_name);
|
||||
|
||||
let stored_captcha = ctx.open_key(&key_name);
|
||||
let stored_captcha = ctx.open_key(&RedisString::create(ctx.ctx, &key_name));
|
||||
if stored_captcha.key_type() == KeyType::Empty {
|
||||
return CacheError::new(format!("key {} not found", key_name)).into();
|
||||
}
|
||||
|
@ -133,7 +134,7 @@ impl MCaptcha {
|
|||
}
|
||||
|
||||
/// Add captcha to redis
|
||||
pub fn add_captcha(ctx: &Context, args: Vec<String>) -> RedisResult {
|
||||
pub fn add_captcha(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
|
||||
let mut args = args.into_iter().skip(1);
|
||||
let key_name = get_captcha_key(&args.next_string()?);
|
||||
let json = args.next_string()?;
|
||||
|
@ -141,7 +142,7 @@ impl MCaptcha {
|
|||
let duration = mcaptcha.duration;
|
||||
let mcaptcha = Self::new(mcaptcha)?;
|
||||
|
||||
let key = ctx.open_key_writable(&key_name);
|
||||
let key = ctx.open_key_writable(&RedisString::create(ctx.ctx, &key_name));
|
||||
if key.key_type() == KeyType::Empty {
|
||||
key.set_value(&MCAPTCHA_MCAPTCHA_TYPE, mcaptcha)?;
|
||||
ctx.log_debug(&format!("mcaptcha {} created", key_name));
|
||||
|
@ -155,11 +156,11 @@ impl MCaptcha {
|
|||
}
|
||||
|
||||
/// check if captcha exists
|
||||
pub fn captcha_exists(ctx: &Context, args: Vec<String>) -> RedisResult {
|
||||
pub fn captcha_exists(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
|
||||
let mut args = args.into_iter().skip(1);
|
||||
let key_name = get_captcha_key(&args.next_string()?);
|
||||
|
||||
let key = ctx.open_key(&key_name);
|
||||
let key = ctx.open_key(&RedisString::create(ctx.ctx, &key_name));
|
||||
if key.key_type() == KeyType::Empty {
|
||||
// 1 is false
|
||||
Ok(RedisValue::Integer(1))
|
||||
|
@ -170,11 +171,11 @@ impl MCaptcha {
|
|||
}
|
||||
|
||||
/// Add captcha to redis
|
||||
pub fn delete_captcha(ctx: &Context, args: Vec<String>) -> RedisResult {
|
||||
pub fn delete_captcha(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
|
||||
let mut args = args.into_iter().skip(1);
|
||||
let key_name = get_captcha_key(&args.next_string()?);
|
||||
|
||||
let key = ctx.open_key_writable(&key_name);
|
||||
let key = ctx.open_key_writable(&RedisString::create(ctx.ctx, &key_name));
|
||||
if key.key_type() == KeyType::Empty {
|
||||
Err(RedisError::nonexistent_key())
|
||||
} else {
|
||||
|
|
|
@ -19,8 +19,8 @@ use std::time::Duration;
|
|||
use redis_module::key::RedisKeyWritable;
|
||||
use redis_module::native_types::RedisType;
|
||||
use redis_module::raw::KeyType;
|
||||
use redis_module::NotifyEvent;
|
||||
use redis_module::{raw, Context};
|
||||
use redis_module::{NotifyEvent, RedisString};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::bucket::Bucket;
|
||||
|
@ -44,7 +44,7 @@ impl MCaptchaSafety {
|
|||
return;
|
||||
}
|
||||
let mcaptcha_name = mcaptcha_name.unwrap();
|
||||
let mcaptcha = ctx.open_key(mcaptcha_name);
|
||||
let mcaptcha = ctx.open_key(&RedisString::create(ctx.ctx, &mcaptcha_name));
|
||||
if mcaptcha.key_type() == KeyType::Empty {
|
||||
ctx.log_warning(&format!("mcaptcha {} is empty", mcaptcha_name));
|
||||
return;
|
||||
|
@ -97,7 +97,7 @@ impl MCaptchaSafety {
|
|||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(ctx: &Context, duration: u64, mcaptcha_name: &str) -> CacheResult<()> {
|
||||
let safety_name = get_safety_name(mcaptcha_name);
|
||||
let safety = ctx.open_key_writable(&safety_name);
|
||||
let safety = ctx.open_key_writable(&RedisString::create(ctx.ctx, &safety_name));
|
||||
|
||||
if safety.key_type() == KeyType::Empty {
|
||||
let safety_val = MCaptchaSafety {};
|
||||
|
@ -126,7 +126,7 @@ impl MCaptchaSafety {
|
|||
|
||||
/// executes when timer goes off. Refreshes expiry timer and resets timer
|
||||
fn boost(ctx: &Context, (safety_name, duration): (String, u64)) {
|
||||
let safety = ctx.open_key_writable(&safety_name);
|
||||
let safety = ctx.open_key_writable(&RedisString::create(ctx.ctx, &safety_name));
|
||||
|
||||
match safety.get_value::<Self>(&MCAPTCHA_SAFETY_TYPE) {
|
||||
Ok(Some(_safety_val)) => match Self::set_timer(ctx, &safety, (safety_name, duration)) {
|
||||
|
@ -139,7 +139,7 @@ impl MCaptchaSafety {
|
|||
return;
|
||||
}
|
||||
let mcaptcha_name = mcaptcha_name.unwrap();
|
||||
let mcaptcha = ctx.open_key(&mcaptcha_name);
|
||||
let mcaptcha = ctx.open_key(&RedisString::create(ctx.ctx, &mcaptcha_name));
|
||||
if mcaptcha.key_type() == KeyType::Empty {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use std::fmt::Display;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::errors::*;
|
||||
|
@ -49,7 +50,7 @@ pub fn get_bucket_instant(duration: u64) -> CacheResult<u64> {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_captcha_key(name: &str) -> String {
|
||||
pub fn get_captcha_key<T: Display>(name: &T) -> String {
|
||||
format!("{}{{{}}}", &*PREFIX_CAPTCHA, name)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue