mirror of
https://github.com/telemt/telemt.git
synced 2026-04-25 22:44:10 +03:00
feat(api): support null-removal in PATCH /v1/users/{user}
PatchUserRequest now uses Patch<T> for the five removable fields (user_ad_tag, max_tcp_conns, expiration_rfc3339, data_quota_bytes, max_unique_ips). Sending JSON null drops the entry from the corresponding access HashMap; sending 0 is preserved as a literal limit; omitted fields stay untouched. The handler synchronises the in-memory ip_tracker on both set and remove of max_unique_ips. A helper parse_patch_expiration mirrors parse_optional_expiration for the new three-state field. Runtime semantics are unchanged.
This commit is contained in:
@@ -5,6 +5,7 @@ use chrono::{DateTime, Utc};
|
||||
use hyper::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::patch::{Patch, patch_field};
|
||||
use crate::crypto::SecureRandom;
|
||||
|
||||
const MAX_USERNAME_LEN: usize = 64;
|
||||
@@ -507,11 +508,16 @@ pub(super) struct CreateUserRequest {
|
||||
#[derive(Deserialize)]
|
||||
pub(super) struct PatchUserRequest {
|
||||
pub(super) secret: Option<String>,
|
||||
pub(super) user_ad_tag: Option<String>,
|
||||
pub(super) max_tcp_conns: Option<usize>,
|
||||
pub(super) expiration_rfc3339: Option<String>,
|
||||
pub(super) data_quota_bytes: Option<u64>,
|
||||
pub(super) max_unique_ips: Option<usize>,
|
||||
#[serde(default, deserialize_with = "patch_field")]
|
||||
pub(super) user_ad_tag: Patch<String>,
|
||||
#[serde(default, deserialize_with = "patch_field")]
|
||||
pub(super) max_tcp_conns: Patch<usize>,
|
||||
#[serde(default, deserialize_with = "patch_field")]
|
||||
pub(super) expiration_rfc3339: Patch<String>,
|
||||
#[serde(default, deserialize_with = "patch_field")]
|
||||
pub(super) data_quota_bytes: Patch<u64>,
|
||||
#[serde(default, deserialize_with = "patch_field")]
|
||||
pub(super) max_unique_ips: Patch<usize>,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize)]
|
||||
@@ -530,6 +536,20 @@ pub(super) fn parse_optional_expiration(
|
||||
Ok(Some(parsed.with_timezone(&Utc)))
|
||||
}
|
||||
|
||||
pub(super) fn parse_patch_expiration(
|
||||
value: &Patch<String>,
|
||||
) -> Result<Patch<DateTime<Utc>>, ApiFailure> {
|
||||
match value {
|
||||
Patch::Unchanged => Ok(Patch::Unchanged),
|
||||
Patch::Remove => Ok(Patch::Remove),
|
||||
Patch::Set(raw) => {
|
||||
let parsed = DateTime::parse_from_rfc3339(raw)
|
||||
.map_err(|_| ApiFailure::bad_request("expiration_rfc3339 must be valid RFC3339"))?;
|
||||
Ok(Patch::Set(parsed.with_timezone(&Utc)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_valid_user_secret(secret: &str) -> bool {
|
||||
secret.len() == 32 && secret.chars().all(|c| c.is_ascii_hexdigit())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user