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,
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 = create_api_key(api_key_request, ThinData(app_state)).await;
198
199 assert!(result.is_ok());
200 let response = result.unwrap();
201 assert_eq!(response.status(), 201);
202 }
203
204 #[actix_web::test]
205 async fn test_list_api_keys_empty() {
206 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
207 let query = PaginationQuery {
208 page: 1,
209 per_page: 10,
210 };
211
212 let result = list_api_keys(query, ThinData(app_state)).await;
213
214 assert!(result.is_ok());
215 let response = result.unwrap();
216 assert_eq!(response.status(), 200);
217 }
218
219 #[actix_web::test]
220 async fn test_list_api_keys_with_data() {
221 let api_key = create_test_api_key_model("test-api-key-1");
222 let app_state =
223 create_mock_app_state(Some(vec![api_key]), None, None, None, None, None).await;
224 let query = PaginationQuery {
225 page: 1,
226 per_page: 10,
227 };
228
229 let result = list_api_keys(query, ThinData(app_state)).await;
230
231 assert!(result.is_ok());
232 let response = result.unwrap();
233 assert_eq!(response.status(), 200);
234 }
235
236 #[actix_web::test]
237 async fn test_get_api_key_permissions() {
238 let api_key = create_test_api_key_model("test-api-key-1");
239 let api_key_id = api_key.id.clone();
240 let app_state =
241 create_mock_app_state(Some(vec![api_key]), None, None, None, None, None).await;
242
243 let result = get_api_key_permissions(api_key_id, ThinData(app_state)).await;
244
245 assert!(result.is_ok());
246 let response = result.unwrap();
247 assert_eq!(response.status(), 200);
248 }
249
250 #[actix_web::test]
265 async fn test_get_permissions_nonexistent_api_key() {
266 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
267
268 let result =
269 get_api_key_permissions("nonexistent-id".to_string(), ThinData(app_state)).await;
270
271 assert!(result.is_err());
272 }
273}