use std::path::Path;
#[derive(Debug, Clone)]
pub struct PendingInvite {
pub invite_code: String,
pub install_uuid: uuid::Uuid,
pub attempted_at: i64,
}
pub fn load(atomcode_dir: &Path) -> Option<PendingInvite> {
let path = atomcode_dir.join("pending_invite");
let content = std::fs::read_to_string(&path).ok()?;
let invite = parse_key_value(&content)?;
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.ok()?
.as_secs() as i64;
if now.saturating_sub(invite.attempted_at) > 30 * 24 * 3600 {
return None;
}
if invite.invite_code.len() != 8
|| !invite
.invite_code
.chars()
.all(|c| c.is_ascii_alphanumeric())
{
return None;
}
Some(invite)
}
fn parse_key_value(content: &str) -> Option<PendingInvite> {
let mut invite_code = None;
let mut install_uuid = None;
let mut attempted_at = None;
for line in content.lines() {
if let Some((k, v)) = line.split_once('=') {
match k.trim() {
"invite_code" => invite_code = Some(v.trim().to_string()),
"install_uuid" => install_uuid = v.trim().parse::<uuid::Uuid>().ok(),
"attempted_at" => attempted_at = v.trim().parse::<i64>().ok(),
_ => {}
}
}
}
Some(PendingInvite {
invite_code: invite_code?,
install_uuid: install_uuid?,
attempted_at: attempted_at?,
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
fn write_temp(content: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("atomcode-test-{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&dir).ok();
let path = dir.join("pending_invite");
let mut f = std::fs::File::create(&path).unwrap();
f.write_all(content.as_bytes()).unwrap();
dir
}
#[test]
fn parses_valid_file() {
let dir = write_temp(
"invite_code=ABC12345\ninstall_uuid=550e8400-e29b-41d4-a716-446655440000\nattempted_at=1800000000\n",
);
let invite = load(&dir).unwrap();
assert_eq!(invite.invite_code, "ABC12345");
assert_eq!(
invite.install_uuid,
uuid::Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap()
);
assert_eq!(invite.attempted_at, 1800000000);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn rejects_malformed_invite_code() {
let dir = write_temp(
"invite_code=bad!\ninstall_uuid=550e8400-e29b-41d4-a716-446655440000\nattempted_at=1800000000\n",
);
assert!(load(&dir).is_none());
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn rejects_invalid_uuid() {
let dir =
write_temp("invite_code=ABC12345\ninstall_uuid=not-a-uuid\nattempted_at=1800000000\n");
assert!(load(&dir).is_none());
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn rejects_expired() {
let dir = write_temp(
"invite_code=ABC12345\ninstall_uuid=550e8400-e29b-41d4-a716-446655440000\nattempted_at=1000000\n",
);
assert!(load(&dir).is_none());
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn returns_none_for_missing_file() {
let dir = std::path::PathBuf::from("/nonexistent/path");
assert!(load(&dir).is_none());
}
}