mirror of
https://github.com/perfect-panel/ppanel-web.git
synced 2026-08-29 05:52:08 -04:00
add mysql2postgres
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "turnstile"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Cloudflare Turnstile token verification — port of pkg/turnstile"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
tokio = { version = "1", features = ["time"] }
|
||||||
|
uuid = { version = "1", features = ["v4"] }
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
//! Cloudflare Turnstile token verification.
|
||||||
|
//! Port of `server/pkg/turnstile`.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
const VERIFY_URL: &str = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct VerifyRequest<'a> {
|
||||||
|
secret: &'a str,
|
||||||
|
response: &'a str,
|
||||||
|
#[serde(skip_serializing_if = "str::is_empty")]
|
||||||
|
remoteip: &'a str,
|
||||||
|
#[serde(skip_serializing_if = "str::is_empty")]
|
||||||
|
idempotency_key: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct VerifyResponse {
|
||||||
|
success: bool,
|
||||||
|
#[serde(rename = "error-codes", default)]
|
||||||
|
error_codes: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify a Turnstile challenge token.
|
||||||
|
///
|
||||||
|
/// - `secret` — site secret key from Cloudflare dashboard
|
||||||
|
/// - `token` — value of `cf-turnstile-response` submitted by the browser
|
||||||
|
/// - `ip` — optional client IP (pass `""` to omit)
|
||||||
|
pub async fn verify(secret: &str, token: &str, ip: &str) -> anyhow::Result<bool> {
|
||||||
|
verify_with_key(secret, token, ip, "").await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify with idempotency key (prevents the same token being accepted twice).
|
||||||
|
pub async fn verify_with_key(
|
||||||
|
secret: &str,
|
||||||
|
token: &str,
|
||||||
|
ip: &str,
|
||||||
|
idempotency_key: &str,
|
||||||
|
) -> anyhow::Result<bool> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let req = VerifyRequest { secret, response: token, remoteip: ip, idempotency_key };
|
||||||
|
let resp: VerifyResponse = client
|
||||||
|
.post(VERIFY_URL)
|
||||||
|
.json(&req)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !resp.success && !resp.error_codes.is_empty() {
|
||||||
|
tracing::warn!(codes = ?resp.error_codes, "turnstile verification failed");
|
||||||
|
}
|
||||||
|
Ok(resp.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a random UUID suitable for use as an idempotency key.
|
||||||
|
pub fn random_uuid() -> String {
|
||||||
|
uuid::Uuid::new_v4().to_string()
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
//! Exchange rate conversion via apilayer.com.
|
||||||
|
//! Port of `server/pkg/exchangeRate/exchangeRate.go`.
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
const API_BASE: &str = "https://api.apilayer.com";
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct ConvertResponse {
|
||||||
|
success: bool,
|
||||||
|
result: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert `amount` from currency `from` to currency `to`.
|
||||||
|
///
|
||||||
|
/// - `access_key` — API key from apilayer.com (configured in `Config.Currency.AccessKey`)
|
||||||
|
///
|
||||||
|
/// Returns the converted amount, or an error if the request fails or the API
|
||||||
|
/// reports failure.
|
||||||
|
pub async fn convert(
|
||||||
|
from: &str,
|
||||||
|
to: &str,
|
||||||
|
amount: f64,
|
||||||
|
access_key: &str,
|
||||||
|
) -> anyhow::Result<f64> {
|
||||||
|
let client = reqwest::Client::builder()
|
||||||
|
.timeout(std::time::Duration::from_secs(5))
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let resp: ConvertResponse = client
|
||||||
|
.get(format!("{API_BASE}/currency_data/convert"))
|
||||||
|
.header("apikey", access_key)
|
||||||
|
.query(&[
|
||||||
|
("from", from),
|
||||||
|
("to", to),
|
||||||
|
("amount", &amount.to_string()),
|
||||||
|
])
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !resp.success {
|
||||||
|
anyhow::bail!("exchange rate API returned failure for {from}→{to}");
|
||||||
|
}
|
||||||
|
resp.result
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("exchange rate API returned no result"))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user