1use crate::{
8 jobs::JobProducerTrait,
9 models::{
10 ApiError, ApiKeyRepoModel, ApiKeyRequest, ApiKeyResponse, ApiResponse, NetworkRepoModel,
11 NotificationRepoModel, PaginationMeta, PaginationQuery, RelayerRepoModel, SignerRepoModel,
12 ThinDataAppState, TransactionRepoModel,
13 },
14 repositories::{
15 ApiKeyRepositoryTrait, NetworkRepository, PluginRepositoryTrait, RelayerRepository,
16 Repository, TransactionCounterTrait, TransactionRepository,
17 },
18};
19use actix_web::HttpResponse;
20use eyre::Result;
21
22pub async fn create_api_key<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
36 api_key_request: ApiKeyRequest,
37 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
38) -> Result<HttpResponse, ApiError>
39where
40 J: JobProducerTrait + Send + Sync + 'static,
41 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
42 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
43 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
44 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
45 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
46 TCR: TransactionCounterTrait + Send + Sync + 'static,
47 PR: PluginRepositoryTrait + Send + Sync + 'static,
48 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
49{
50 let api_key = ApiKeyRepoModel::try_from(api_key_request)?;
51
52 let api_key = state.api_key_repository.create(api_key).await?;
53
54 Ok(HttpResponse::Created().json(ApiResponse::success(api_key)))
55}
56
57pub async fn list_api_keys<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
70 query: PaginationQuery,
71 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
72) -> Result<HttpResponse, ApiError>
73where
74 J: JobProducerTrait + Send + Sync + 'static,
75 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
76 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
77 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
78 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
79 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
80 TCR: TransactionCounterTrait + Send + Sync + 'static,
81 PR: PluginRepositoryTrait + Send + Sync + 'static,
82 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
83{
84 let api_keys = state.api_key_repository.list_paginated(query).await?;
85
86 let api_key_items: Vec<ApiKeyRepoModel> = api_keys.items.into_iter().collect();
87
88 let api_key_items: Vec<ApiKeyResponse> = api_key_items
90 .into_iter()
91 .map(ApiKeyResponse::try_from)
92 .collect::<Result<Vec<ApiKeyResponse>, ApiError>>()?;
93
94 Ok(HttpResponse::Ok().json(ApiResponse::paginated(
95 api_key_items,
96 PaginationMeta {
97 total_items: api_keys.total,
98 current_page: api_keys.page,
99 per_page: api_keys.per_page,
100 },
101 )))
102}
103
104pub async fn get_api_key_permissions<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
112 api_key_id: String,
113 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
114) -> Result<HttpResponse, ApiError>
115where
116 J: JobProducerTrait + Send + Sync + 'static,
117 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
118 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
119 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
120 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
121 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
122 TCR: TransactionCounterTrait + Send + Sync + 'static,
123 PR: PluginRepositoryTrait + Send + Sync + 'static,
124 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
125{
126 let permissions = state
127 .api_key_repository
128 .list_permissions(&api_key_id)
129 .await?;
130
131 Ok(HttpResponse::Ok().json(ApiResponse::success(permissions)))
132}
133
134pub async fn delete_api_key<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
142 _api_key_id: String,
143 _state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
144) -> Result<HttpResponse, ApiError>
145where
146 J: JobProducerTrait + Send + Sync + 'static,
147 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
148 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
149 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
150 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
151 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
152 TCR: TransactionCounterTrait + Send + Sync + 'static,
153 PR: PluginRepositoryTrait + Send + Sync + 'static,
154 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
155{
156 Ok(HttpResponse::Ok().json(ApiResponse::<String>::error("Not implemented".to_string())))
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use crate::{
166 models::{ApiKeyRepoModel, PaginationQuery, SecretString},
167 utils::{mocks::mockutils::create_mock_app_state, EncryptionContext},
168 };
169 use actix_web::web::ThinData;
170
171 fn create_test_api_key_model(id: &str) -> ApiKeyRepoModel {
173 ApiKeyRepoModel {
174 id: id.to_string(),
175 value: SecretString::new("test-api-key-value"),
176 name: "Test API Key".to_string(),
177 allowed_origins: vec!["*".to_string()],
178 permissions: vec!["relayer:all:execute".to_string()],
179 created_at: "2023-01-01T00:00:00Z".to_string(),
180 }
181 }
182
183 fn create_test_api_key_create_request(name: &str) -> ApiKeyRequest {
185 ApiKeyRequest {
186 name: name.to_string(),
187 permissions: vec!["relayer:all:execute".to_string()],
188 allowed_origins: Some(vec!["*".to_string()]),
189 }
190 }
191
192 #[actix_web::test]
193 async fn test_create_api_key() {
194 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
195 let api_key_request = create_test_api_key_create_request("Test API Key");
196
197 let result = EncryptionContext::with_aad("test-api-key".to_string(), || async {
198 create_api_key(api_key_request, ThinData(app_state)).await
199 })
200 .await;
201
202 assert!(result.is_ok());
203 let response = result.unwrap();
204 assert_eq!(response.status(), 201);
205 }
206
207 #[actix_web::test]
208 async fn test_list_api_keys_empty() {
209 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
210 let query = PaginationQuery {
211 page: 1,
212 per_page: 10,
213 };
214
215 let result = list_api_keys(query, ThinData(app_state)).await;
216
217 assert!(result.is_ok());
218 let response = result.unwrap();
219 assert_eq!(response.status(), 200);
220 }
221
222 #[actix_web::test]
223 async fn test_list_api_keys_with_data() {
224 let api_key = create_test_api_key_model("test-api-key-1");
225 let app_state =
226 create_mock_app_state(Some(vec![api_key]), None, None, None, None, None).await;
227 let query = PaginationQuery {
228 page: 1,
229 per_page: 10,
230 };
231
232 let result = list_api_keys(query, ThinData(app_state)).await;
233
234 assert!(result.is_ok());
235 let response = result.unwrap();
236 assert_eq!(response.status(), 200);
237 }
238
239 #[actix_web::test]
240 async fn test_get_api_key_permissions() {
241 let api_key = create_test_api_key_model("test-api-key-1");
242 let api_key_id = api_key.id.clone();
243 let app_state =
244 create_mock_app_state(Some(vec![api_key]), None, None, None, None, None).await;
245
246 let result = get_api_key_permissions(api_key_id, ThinData(app_state)).await;
247
248 assert!(result.is_ok());
249 let response = result.unwrap();
250 assert_eq!(response.status(), 200);
251 }
252
253 #[actix_web::test]
268 async fn test_get_permissions_nonexistent_api_key() {
269 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
270
271 let result =
272 get_api_key_permissions("nonexistent-id".to_string(), ThinData(app_state)).await;
273
274 assert!(result.is_err());
275 }
276}