ironflow_store/entities/
api_key.rs1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use crate::entities::api_key_scope::ApiKeyScope;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ApiKey {
12 pub id: Uuid,
14 pub user_id: Uuid,
16 pub name: String,
18 #[serde(skip_serializing)]
20 pub key_hash: String,
21 pub key_prefix: String,
23 pub scopes: Vec<ApiKeyScope>,
25 pub is_active: bool,
27 pub expires_at: Option<DateTime<Utc>>,
29 pub last_used_at: Option<DateTime<Utc>>,
31 pub created_at: DateTime<Utc>,
33 pub updated_at: DateTime<Utc>,
35}
36
37#[derive(Debug, Clone)]
39pub struct NewApiKey {
40 pub user_id: Uuid,
42 pub name: String,
44 pub key_hash: String,
46 pub key_prefix: String,
48 pub scopes: Vec<ApiKeyScope>,
50 pub expires_at: Option<DateTime<Utc>>,
52}
53
54#[derive(Debug, Clone, Default)]
56pub struct ApiKeyUpdate {
57 pub name: Option<String>,
59 pub scopes: Option<Vec<ApiKeyScope>>,
61 pub is_active: Option<bool>,
63 pub expires_at: Option<Option<DateTime<Utc>>>,
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn api_key_serde_excludes_hash() {
73 let key = ApiKey {
74 id: Uuid::now_v7(),
75 user_id: Uuid::now_v7(),
76 name: "test-key".to_string(),
77 key_hash: "secret_hash".to_string(),
78 key_prefix: "irfl_abc".to_string(),
79 scopes: vec![ApiKeyScope::RunsRead],
80 is_active: true,
81 expires_at: None,
82 last_used_at: None,
83 created_at: Utc::now(),
84 updated_at: Utc::now(),
85 };
86
87 let json = serde_json::to_string(&key).expect("serialize");
88 assert!(!json.contains("secret_hash"));
89 assert!(json.contains("test-key"));
90 assert!(json.contains("irfl_abc"));
91 }
92
93 #[test]
94 fn api_key_update_default_is_empty() {
95 let update = ApiKeyUpdate::default();
96 assert!(update.name.is_none());
97 assert!(update.scopes.is_none());
98 assert!(update.is_active.is_none());
99 assert!(update.expires_at.is_none());
100 }
101}