Skip to content

Commit 3815fc8

Browse files
committed
feat(api): implement all apikeys handlers
1 parent cf085a5 commit 3815fc8

6 files changed

Lines changed: 219 additions & 0 deletions

File tree

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use crate::db::operations::{create_api_key, generate_api_key};
2+
use crate::db::repr::Permission;
3+
use crate::prelude::types::*;
4+
use axum::Json;
5+
use axum::extract::State;
6+
use axum::http::StatusCode;
7+
use serde::{Deserialize, Serialize};
8+
use std::sync::Arc;
9+
use tokio::sync::Mutex;
10+
11+
#[derive(Deserialize)]
12+
pub struct CreateApiKeyRequest {
13+
pub name: String,
14+
pub permissions: Vec<Permission>,
15+
pub expires_at: Option<i64>,
16+
}
17+
18+
#[derive(Serialize)]
19+
pub struct CreateApiKeyResponse {
20+
pub api_key: String,
21+
pub id: i64,
22+
pub name: String,
23+
pub permissions: Vec<Permission>,
24+
pub created_at: i64,
25+
pub expires_at: Option<i64>,
26+
}
27+
28+
pub async fn create_api_key_handler(
29+
State(bot_state): State<Arc<Mutex<BotState>>>,
30+
Json(req): Json<CreateApiKeyRequest>,
31+
) -> Result<Json<CreateApiKeyResponse>, (StatusCode, String)> {
32+
if req.name.trim().is_empty() {
33+
return Err((StatusCode::BAD_REQUEST, "Name cannot be empty".to_string()));
34+
}
35+
36+
if req.permissions.is_empty() {
37+
return Err((
38+
StatusCode::BAD_REQUEST,
39+
"At least one permission is required".to_string(),
40+
));
41+
}
42+
43+
let db_pool = {
44+
let state_lock = bot_state.lock().await;
45+
match &state_lock.db_pool {
46+
Some(pool) => pool.clone(),
47+
None => {
48+
return Err((
49+
StatusCode::INTERNAL_SERVER_ERROR,
50+
"Database not initialized".to_string(),
51+
));
52+
}
53+
}
54+
};
55+
56+
let (plain_key, key_hash) = match generate_api_key() {
57+
Ok(keys) => keys,
58+
Err(e) => return Err((StatusCode::INTERNAL_SERVER_ERROR, e)),
59+
};
60+
61+
let api_key = match create_api_key(
62+
&db_pool,
63+
key_hash,
64+
req.name,
65+
req.permissions,
66+
req.expires_at,
67+
)
68+
.await
69+
{
70+
Ok(key) => key,
71+
Err(e) => return Err((StatusCode::INTERNAL_SERVER_ERROR, e)),
72+
};
73+
74+
Ok(Json(CreateApiKeyResponse {
75+
api_key: plain_key,
76+
id: api_key.id,
77+
name: api_key.name,
78+
permissions: api_key.permissions,
79+
created_at: api_key.created_at,
80+
expires_at: api_key.expires_at,
81+
}))
82+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use crate::db::operations::delete_api_key;
2+
use crate::prelude::types::*;
3+
use axum::extract::{Path, State};
4+
use axum::http::StatusCode;
5+
use std::sync::Arc;
6+
use tokio::sync::Mutex;
7+
8+
pub async fn delete_api_key_handler(
9+
State(bot_state): State<Arc<Mutex<BotState>>>,
10+
Path(id): Path<i64>,
11+
) -> Result<StatusCode, (StatusCode, String)> {
12+
let db_pool = {
13+
let state_lock = bot_state.lock().await;
14+
match &state_lock.db_pool {
15+
Some(pool) => pool.clone(),
16+
None => {
17+
return Err((
18+
StatusCode::INTERNAL_SERVER_ERROR,
19+
"Database not initialized".to_string(),
20+
))
21+
}
22+
}
23+
};
24+
25+
match delete_api_key(&db_pool, id).await {
26+
Ok(_) => Ok(StatusCode::NO_CONTENT),
27+
Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e)),
28+
}
29+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use crate::db::operations::list_api_keys;
2+
use crate::db::repr::{ApiKey, Permission};
3+
use crate::prelude::types::*;
4+
use axum::Json;
5+
use axum::extract::State;
6+
use axum::http::StatusCode;
7+
use serde::Serialize;
8+
use std::sync::Arc;
9+
use tokio::sync::Mutex;
10+
11+
#[derive(Serialize)]
12+
pub struct ApiKeyListItem {
13+
pub id: i64,
14+
pub name: String,
15+
pub permissions: Vec<Permission>,
16+
pub created_at: i64,
17+
pub expires_at: Option<i64>,
18+
pub last_used_at: Option<i64>,
19+
pub is_active: bool,
20+
pub key_preview: String,
21+
}
22+
23+
impl From<ApiKey> for ApiKeyListItem {
24+
fn from(key: ApiKey) -> Self {
25+
let key_preview = if key.key_hash.len() > 12 {
26+
format!("{}...", &key.key_hash[..12])
27+
} else {
28+
key.key_hash.clone()
29+
};
30+
31+
ApiKeyListItem {
32+
id: key.id,
33+
name: key.name,
34+
permissions: key.permissions,
35+
created_at: key.created_at,
36+
expires_at: key.expires_at,
37+
last_used_at: key.last_used_at,
38+
is_active: key.is_active,
39+
key_preview,
40+
}
41+
}
42+
}
43+
44+
pub async fn list_api_keys_handler(
45+
State(bot_state): State<Arc<Mutex<BotState>>>,
46+
) -> Result<Json<Vec<ApiKeyListItem>>, (StatusCode, String)> {
47+
let db_pool = {
48+
let state_lock = bot_state.lock().await;
49+
match &state_lock.db_pool {
50+
Some(pool) => pool.clone(),
51+
None => {
52+
return Err((
53+
StatusCode::INTERNAL_SERVER_ERROR,
54+
"Database not initialized".to_string(),
55+
));
56+
}
57+
}
58+
};
59+
60+
let api_keys = match list_api_keys(&db_pool).await {
61+
Ok(keys) => keys,
62+
Err(e) => return Err((StatusCode::INTERNAL_SERVER_ERROR, e)),
63+
};
64+
65+
let response: Vec<ApiKeyListItem> = api_keys.into_iter().map(|k| k.into()).collect();
66+
67+
Ok(Json(response))
68+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
mod create;
2+
mod delete;
3+
mod list;
4+
mod revoke;
5+
6+
pub use create::*;
7+
pub use delete::*;
8+
pub use list::*;
9+
pub use revoke::*;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use crate::db::operations::revoke_api_key;
2+
use crate::prelude::types::*;
3+
use axum::extract::{Path, State};
4+
use axum::http::StatusCode;
5+
use std::sync::Arc;
6+
use tokio::sync::Mutex;
7+
8+
pub async fn revoke_api_key_handler(
9+
State(bot_state): State<Arc<Mutex<BotState>>>,
10+
Path(id): Path<i64>,
11+
) -> Result<StatusCode, (StatusCode, String)> {
12+
let db_pool = {
13+
let state_lock = bot_state.lock().await;
14+
match &state_lock.db_pool {
15+
Some(pool) => pool.clone(),
16+
None => {
17+
return Err((
18+
StatusCode::INTERNAL_SERVER_ERROR,
19+
"Database not initialized".to_string(),
20+
))
21+
}
22+
}
23+
};
24+
25+
match revoke_api_key(&db_pool, id).await {
26+
Ok(_) => Ok(StatusCode::NO_CONTENT),
27+
Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e)),
28+
}
29+
}

rustmail/src/api/handler/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
pub mod apikeys;
12
pub mod auth;
23
pub mod bot;
34
pub mod externals;
45
pub mod panel;
56
pub mod user;
67

8+
pub use apikeys::*;
79
pub use auth::*;
810
pub use bot::*;
911
pub use externals::*;

0 commit comments

Comments
 (0)