Compare commits
No commits in common. "main" and "0.0.2" have entirely different histories.
14 changed files with 73 additions and 870 deletions
159
Cargo.lock
generated
159
Cargo.lock
generated
|
|
@ -120,15 +120,6 @@ version = "1.0.101"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
|
|
@ -438,8 +429,6 @@ dependencies = [
|
|||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"urlencoding",
|
||||
"utoipa",
|
||||
"utoipa-swagger-ui",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
|
@ -775,17 +764,6 @@ dependencies = [
|
|||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.115",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "2.1.1"
|
||||
|
|
@ -994,7 +972,6 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
|||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2641,40 +2618,6 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"syn 2.0.115",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
|
||||
dependencies = [
|
||||
"sha2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust_decimal"
|
||||
version = "1.40.0"
|
||||
|
|
@ -2766,15 +2709,6 @@ version = "1.0.23"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.28"
|
||||
|
|
@ -4011,48 +3945,6 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "utoipa"
|
||||
version = "5.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993"
|
||||
dependencies = [
|
||||
"indexmap 2.13.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"utoipa-gen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utoipa-gen"
|
||||
version = "5.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 2.0.115",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utoipa-swagger-ui"
|
||||
version = "9.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"base64 0.22.1",
|
||||
"mime_guess",
|
||||
"regex",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
"utoipa",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.21.0"
|
||||
|
|
@ -4083,16 +3975,6 @@ version = "0.9.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
|
|
@ -4273,15 +4155,6 @@ dependencies = [
|
|||
"wasite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
|
|
@ -4795,44 +4668,12 @@ dependencies = [
|
|||
"syn 2.0.115",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"indexmap 2.13.0",
|
||||
"memchr",
|
||||
"zopfli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crc32fast",
|
||||
"log",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
|
|
|
|||
|
|
@ -22,5 +22,3 @@ jsonwebtoken = "9.3.1"
|
|||
anyhow = "1.0.95"
|
||||
reqwest = { version = "0.12.12", features = ["json", "rustls-tls"] }
|
||||
serde_json = "1.0.138"
|
||||
utoipa = { version = "5.4.0", features = ["axum_extras", "chrono", "macros"] }
|
||||
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ pub use sea_orm_migration::prelude::*;
|
|||
|
||||
mod m20220101_000001_create_event_table;
|
||||
mod m20250215_213300_create_user_table;
|
||||
mod m20260216_215000_refactor_user_id_to_sub;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
|
|
@ -12,7 +11,6 @@ impl MigratorTrait for Migrator {
|
|||
vec![
|
||||
Box::new(m20220101_000001_create_event_table::Migration),
|
||||
Box::new(m20250215_213300_create_user_table::Migration),
|
||||
Box::new(m20260216_215000_refactor_user_id_to_sub::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,123 +0,0 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// Drop foreign key
|
||||
manager
|
||||
.drop_foreign_key(
|
||||
ForeignKey::drop()
|
||||
.name("fk-event-user_id")
|
||||
.table(Event::Table)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Add user_sub column
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Event::Table)
|
||||
.add_column(ColumnDef::new(Event::UserSub).string().null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Copy user_sub from user table via user_id
|
||||
manager
|
||||
.exec_stmt(
|
||||
Query::update()
|
||||
.table(Event::Table)
|
||||
.values([(
|
||||
Event::UserSub,
|
||||
SimpleExpr::SubQuery(
|
||||
None,
|
||||
Box::new(
|
||||
Query::select()
|
||||
.column(User::Sub)
|
||||
.from(User::Table)
|
||||
.and_where(
|
||||
Expr::col((User::Table, User::Id))
|
||||
.eq(Expr::col((Event::Table, Event::UserId))),
|
||||
)
|
||||
.to_owned()
|
||||
.into_sub_query_statement(),
|
||||
),
|
||||
),
|
||||
)])
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Drop user_id column
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Event::Table)
|
||||
.drop_column(Event::UserId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Create new foreign key to user.sub
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk-event-user_sub")
|
||||
.from(Event::Table, Event::UserSub)
|
||||
.to(User::Table, User::Sub)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_foreign_key(
|
||||
ForeignKey::drop()
|
||||
.name("fk-event-user_sub")
|
||||
.table(Event::Table)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Event::Table)
|
||||
.drop_column(Event::UserSub)
|
||||
.add_column(ColumnDef::new(Event::UserId).integer())
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk-event-user_id")
|
||||
.from(Event::Table, Event::UserId)
|
||||
.to(User::Table, User::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum User {
|
||||
Table,
|
||||
Id,
|
||||
Sub,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Event {
|
||||
Table,
|
||||
UserId,
|
||||
UserSub,
|
||||
}
|
||||
49
src/auth.rs
49
src/auth.rs
|
|
@ -11,7 +11,6 @@ use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Clone, FromRef)]
|
||||
pub struct AppState {
|
||||
|
|
@ -219,10 +218,9 @@ pub struct Claims {
|
|||
pub email: Option<String>,
|
||||
pub preferred_username: Option<String>,
|
||||
pub exp: usize,
|
||||
pub groups: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct CurrentUser {
|
||||
pub id: i32,
|
||||
pub sub: String,
|
||||
|
|
@ -275,48 +273,3 @@ where
|
|||
Err((axum::http::StatusCode::UNAUTHORIZED, "Unauthorized").into_response())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ServiceAccount {
|
||||
pub sub: String,
|
||||
}
|
||||
|
||||
impl<S> FromRequestParts<S> for ServiceAccount
|
||||
where
|
||||
AppState: FromRef<S>,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = Response;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||
let state = AppState::from_ref(state);
|
||||
let oidc = state.oidc;
|
||||
|
||||
let auth_header = parts
|
||||
.headers
|
||||
.get(header::AUTHORIZATION)
|
||||
.and_then(|h| h.to_str().ok());
|
||||
|
||||
if let Some(auth_header) = auth_header {
|
||||
if auth_header.starts_with("Bearer ") {
|
||||
let token = &auth_header[7..];
|
||||
if let Ok(claims) = oidc.validate_token(token).await {
|
||||
if claims
|
||||
.groups
|
||||
.as_ref()
|
||||
.map(|g| g.contains(&"agent".to_string()))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Ok(ServiceAccount { sub: claims.sub });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err((
|
||||
axum::http::StatusCode::UNAUTHORIZED,
|
||||
"Unauthorized Service Account",
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize, ToSchema)]
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "event")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i64,
|
||||
pub user_sub: String,
|
||||
pub user_id: Option<i32>,
|
||||
pub name: String,
|
||||
pub from: DateTime,
|
||||
pub to: DateTime,
|
||||
|
|
@ -17,8 +16,8 @@ pub struct Model {
|
|||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::user::Entity",
|
||||
from = "Column::UserSub",
|
||||
to = "super::user::Column::Sub"
|
||||
from = "Column::UserId",
|
||||
to = "super::user::Column::Id"
|
||||
)]
|
||||
User,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, ToSchema)]
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "user")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::auth::AppState;
|
||||
use crate::entities::user;
|
||||
use crate::handlers::Json;
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Query, State},
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
};
|
||||
|
|
@ -15,15 +15,12 @@ pub struct AuthCallback {
|
|||
pub state: String,
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(state))]
|
||||
pub async fn login(State(state): State<AppState>) -> Response {
|
||||
tracing::info!("Initiating login redirect");
|
||||
let oidc = state.oidc;
|
||||
let (auth_url, _csrf_token, _nonce) = oidc.auth_url();
|
||||
Redirect::to(&auth_url).into_response()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(state, params), fields(code = %params.code))]
|
||||
pub async fn callback(
|
||||
State(state): State<AppState>,
|
||||
Query(params): Query<AuthCallback>,
|
||||
|
|
@ -45,22 +42,14 @@ pub async fn callback(
|
|||
.unwrap_or(None);
|
||||
|
||||
if existing_user.is_none() {
|
||||
tracing::info!("Creating new user for sub {}", sub);
|
||||
let new_user = user::ActiveModel {
|
||||
sub: Set(sub.clone()),
|
||||
sub: Set(sub),
|
||||
email: Set(email),
|
||||
name: Set(name),
|
||||
..Default::default()
|
||||
};
|
||||
new_user.insert(&db).await.unwrap();
|
||||
} else {
|
||||
tracing::debug!("Updating existing user for sub {}", sub);
|
||||
let mut user: user::ActiveModel = existing_user.unwrap().into();
|
||||
user.name = Set(name);
|
||||
user.update(&db).await.unwrap();
|
||||
}
|
||||
|
||||
tracing::info!("User {} logged in successfully", sub);
|
||||
};
|
||||
|
||||
let mut redirect_url = format!("/#access_token={}", tokens.id_token);
|
||||
if let Some(refresh_token) = tokens.refresh_token {
|
||||
|
|
@ -89,7 +78,6 @@ pub async fn callback(
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(state, payload))]
|
||||
pub async fn refresh(
|
||||
State(state): State<AppState>,
|
||||
Json(payload): Json<RefreshRequest>,
|
||||
|
|
@ -97,10 +85,7 @@ pub async fn refresh(
|
|||
let oidc = state.oidc;
|
||||
|
||||
match oidc.refresh_token(payload.refresh_token).await {
|
||||
Ok(tokens) => {
|
||||
tracing::info!("Token refreshed successfully");
|
||||
Json(tokens).into_response()
|
||||
}
|
||||
Ok(tokens) => Json(tokens).into_response(),
|
||||
Err(e) => {
|
||||
tracing::error!("Token refresh error: {:?}", e);
|
||||
(axum::http::StatusCode::UNAUTHORIZED, "Refresh failed").into_response()
|
||||
|
|
@ -108,7 +93,7 @@ pub async fn refresh(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Deserialize)]
|
||||
pub struct RefreshRequest {
|
||||
pub refresh_token: String,
|
||||
}
|
||||
|
|
@ -117,19 +102,18 @@ pub async fn logout() -> Response {
|
|||
Redirect::to("/").into_response()
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/auth/me",
|
||||
responses(
|
||||
(status = 200, description = "Current user profile", body = crate::auth::CurrentUser),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(
|
||||
("oidc" = [])
|
||||
)
|
||||
)]
|
||||
#[tracing::instrument(skip(user))]
|
||||
pub async fn me(user: crate::auth::CurrentUser) -> Json<crate::auth::CurrentUser> {
|
||||
tracing::debug!("User profile requested for {}", user.sub);
|
||||
Json(user)
|
||||
pub async fn me(
|
||||
crate::auth::CurrentUser {
|
||||
id,
|
||||
sub,
|
||||
email,
|
||||
name,
|
||||
}: crate::auth::CurrentUser,
|
||||
) -> Json<crate::auth::CurrentUser> {
|
||||
Json(crate::auth::CurrentUser {
|
||||
id,
|
||||
sub,
|
||||
email,
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,25 @@
|
|||
use crate::auth::{AppState, CurrentUser};
|
||||
use crate::entities::{event, event::Entity as Event};
|
||||
use crate::handlers::Json;
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
};
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use sea_orm::*;
|
||||
use sea_orm::{ActiveValue::Set, sea_query::Expr};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct CreateEventRequest {
|
||||
pub name: String,
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListEventsQuery {
|
||||
pub upcoming: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct EventResponse {
|
||||
pub id: i64,
|
||||
pub user_sub: String,
|
||||
pub name: String,
|
||||
pub from: DateTime<Utc>,
|
||||
pub to: DateTime<Utc>,
|
||||
}
|
||||
|
||||
fn parse_datetime(dt_str: &str) -> Result<chrono::NaiveDateTime, StatusCode> {
|
||||
if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(dt_str, "%Y-%m-%dT%H:%M") {
|
||||
return Ok(dt);
|
||||
|
|
@ -43,23 +30,9 @@ fn parse_datetime(dt_str: &str) -> Result<chrono::NaiveDateTime, StatusCode> {
|
|||
dt_str.parse().map_err(|_| StatusCode::BAD_REQUEST)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/events",
|
||||
request_body = CreateEventRequest,
|
||||
responses(
|
||||
(status = 200, description = "Event created successfully", body = event::Model),
|
||||
(status = 400, description = "Invalid request payload"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(
|
||||
("oidc" = [])
|
||||
)
|
||||
)]
|
||||
#[tracing::instrument(skip(state, payload), fields(user_sub = %sub, event_name = %payload.name))]
|
||||
pub async fn create_event(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser { sub, .. }: CurrentUser,
|
||||
CurrentUser { id, .. }: CurrentUser,
|
||||
Json(payload): Json<CreateEventRequest>,
|
||||
) -> Result<Json<event::Model>, StatusCode> {
|
||||
let db = state.db;
|
||||
|
|
@ -70,7 +43,7 @@ pub async fn create_event(
|
|||
name: Set(payload.name),
|
||||
from: Set(from),
|
||||
to: Set(to),
|
||||
user_sub: Set(sub.clone()),
|
||||
user_id: Set(Some(id)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
@ -79,174 +52,97 @@ pub async fn create_event(
|
|||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
tracing::info!("Created event {} for user {}", result.id, sub);
|
||||
tracing::info!("Created event {} for user {}", result.id, id);
|
||||
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/events",
|
||||
params(ListEventsQuery),
|
||||
responses(
|
||||
(status = 200, description = "List of events", body = [event::Model]),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(
|
||||
("oidc" = [])
|
||||
)
|
||||
)]
|
||||
#[tracing::instrument(skip(state), fields(user_sub = %sub))]
|
||||
pub async fn list_events(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser { sub, .. }: CurrentUser,
|
||||
CurrentUser { id, .. }: CurrentUser,
|
||||
Query(query): Query<ListEventsQuery>,
|
||||
) -> Result<Json<Vec<event::Model>>, StatusCode> {
|
||||
let db = state.db;
|
||||
tracing::debug!("Listing events for user");
|
||||
let mut find = Event::find().filter(event::Column::UserSub.eq(sub.clone()));
|
||||
let mut find = Event::find().filter(event::Column::UserId.eq(id));
|
||||
|
||||
if let Some(true) = query.upcoming {
|
||||
let now = chrono::Utc::now().naive_utc();
|
||||
find = find.filter(event::Column::From.gte(now));
|
||||
}
|
||||
|
||||
let events = find.all(&db).await.map_err(|e| {
|
||||
tracing::error!("Failed to list events: {:?}", e);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
tracing::info!("Listed {} events for user {}", events.len(), sub);
|
||||
let events = find
|
||||
.all(&db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
Ok(Json(events))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/events/{id}",
|
||||
params(
|
||||
("id" = i32, Path, description = "Event database id")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Event details", body = event::Model),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 404, description = "Event not found")
|
||||
),
|
||||
security(
|
||||
("oidc" = [])
|
||||
)
|
||||
)]
|
||||
#[tracing::instrument(skip(state), fields(user_sub = %sub, event_id = %event_id))]
|
||||
pub async fn get_event(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser { sub, .. }: CurrentUser,
|
||||
CurrentUser { id, .. }: CurrentUser,
|
||||
Path(event_id): Path<i32>,
|
||||
) -> Result<Json<event::Model>, StatusCode> {
|
||||
let db = state.db;
|
||||
let event = Event::find_by_id(event_id)
|
||||
.filter(event::Column::UserSub.eq(sub.clone()))
|
||||
.filter(event::Column::UserId.eq(id))
|
||||
.one(&db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Failed to fetch event {}: {:?}", event_id, e);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
tracing::warn!("Event {} not found for user {}", event_id, sub);
|
||||
StatusCode::NOT_FOUND
|
||||
})?;
|
||||
|
||||
tracing::info!("Fetched event {} for user {}", event_id, sub);
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
|
||||
Ok(Json(event))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/events/{id}",
|
||||
params(
|
||||
("id" = i32, Path, description = "Event database id")
|
||||
),
|
||||
request_body = CreateEventRequest,
|
||||
responses(
|
||||
(status = 200, description = "Event updated successfully", body = event::Model),
|
||||
(status = 400, description = "Invalid request payload"),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 404, description = "Event not found")
|
||||
),
|
||||
security(
|
||||
("oidc" = [])
|
||||
)
|
||||
)]
|
||||
#[tracing::instrument(skip(state, payload), fields(user_sub = %sub, event_id = %event_id, event_name = %payload.name))]
|
||||
pub async fn update_event(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser { sub, .. }: CurrentUser,
|
||||
CurrentUser { id, .. }: CurrentUser,
|
||||
Path(event_id): Path<i32>,
|
||||
Json(payload): Json<CreateEventRequest>,
|
||||
) -> Result<Json<EventResponse>, StatusCode> {
|
||||
) -> Result<Json<event::Model>, StatusCode> {
|
||||
let db = state.db;
|
||||
let from = parse_datetime(&payload.from)?;
|
||||
let to = parse_datetime(&payload.to)?;
|
||||
|
||||
let result = Event::update_many()
|
||||
.col_expr(event::Column::Name, Expr::value(payload.name.clone()))
|
||||
.col_expr(event::Column::From, Expr::value(from))
|
||||
.col_expr(event::Column::To, Expr::value(to))
|
||||
.filter(event::Column::Id.eq(event_id as i64))
|
||||
.filter(event::Column::UserSub.eq(sub.clone()))
|
||||
.exec(&db)
|
||||
let event = Event::find_by_id(event_id)
|
||||
.filter(event::Column::UserId.eq(id))
|
||||
.one(&db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Service failed to update event {}: {:?}", event_id, e);
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
|
||||
let mut event: event::ActiveModel = event.into();
|
||||
event.name = Set(payload.name);
|
||||
event.from = Set(from);
|
||||
event.to = Set(to);
|
||||
|
||||
let result = event.update(&db).await.map_err(|e| {
|
||||
tracing::error!("Failed to update event {}: {:?}", event_id, e);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
if result.rows_affected == 0 {
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
}
|
||||
tracing::info!("Updated event {} for user {}", event_id, id);
|
||||
|
||||
Ok(Json(EventResponse {
|
||||
id: event_id as i64,
|
||||
user_sub: sub,
|
||||
name: payload.name,
|
||||
from: Utc.from_utc_datetime(&from),
|
||||
to: Utc.from_utc_datetime(&to),
|
||||
}))
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/events/{id}",
|
||||
params(
|
||||
("id" = i32, Path, description = "Event database id")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Event deleted successfully"),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 404, description = "Event not found")
|
||||
),
|
||||
security(
|
||||
("oidc" = [])
|
||||
)
|
||||
)]
|
||||
#[tracing::instrument(skip(state), fields(user_sub = %sub, event_id = %event_id))]
|
||||
pub async fn delete_event(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser { sub, .. }: CurrentUser,
|
||||
CurrentUser { id, .. }: CurrentUser,
|
||||
Path(event_id): Path<i32>,
|
||||
) -> Result<StatusCode, StatusCode> {
|
||||
let db = state.db;
|
||||
Event::delete_by_id(event_id as i64)
|
||||
.filter(event::Column::UserSub.eq(sub.clone()))
|
||||
.exec(&db)
|
||||
let event = Event::find_by_id(event_id)
|
||||
.filter(event::Column::UserId.eq(id))
|
||||
.one(&db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Failed to delete event {}: {:?}", event_id, e);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
|
||||
tracing::info!("Deleted event {} for user {}", event_id, sub);
|
||||
event
|
||||
.delete(&db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +1,2 @@
|
|||
pub mod auth;
|
||||
pub mod event;
|
||||
pub mod service;
|
||||
|
||||
use axum::{
|
||||
extract::Request,
|
||||
extract::{FromRequest, rejection::JsonRejection},
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
pub struct Json<T>(pub T);
|
||||
|
||||
impl<S, T> FromRequest<S> for Json<T>
|
||||
where
|
||||
axum::Json<T>: FromRequest<S, Rejection = JsonRejection>,
|
||||
S: Send + Sync,
|
||||
T: 'static,
|
||||
{
|
||||
type Rejection = Response;
|
||||
|
||||
fn from_request(
|
||||
req: Request,
|
||||
state: &S,
|
||||
) -> Pin<Box<dyn Future<Output = Result<Self, Self::Rejection>> + Send + '_>> {
|
||||
let fut = axum::Json::<T>::from_request(req, state);
|
||||
Box::pin(async move {
|
||||
match fut.await {
|
||||
Ok(value) => Ok(Self(value.0)),
|
||||
Err(rejection) => {
|
||||
tracing::error!(%rejection, "Json extraction failed");
|
||||
Err((StatusCode::BAD_REQUEST, rejection.body_text()).into_response())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoResponse for Json<T>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
axum::Json(self.0).into_response()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,228 +0,0 @@
|
|||
use crate::auth::{AppState, ServiceAccount};
|
||||
use crate::entities::{event, event::Entity as Event};
|
||||
use crate::handlers::Json;
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
};
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use sea_orm::{ActiveValue::Set, sea_query::Expr, *};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct ServiceEventResponse {
|
||||
pub id: i64,
|
||||
pub user_sub: String,
|
||||
pub name: String,
|
||||
pub from: DateTime<Utc>,
|
||||
pub to: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl From<event::Model> for ServiceEventResponse {
|
||||
fn from(m: event::Model) -> Self {
|
||||
Self {
|
||||
id: m.id,
|
||||
user_sub: m.user_sub,
|
||||
name: m.name,
|
||||
from: Utc.from_utc_datetime(&m.from),
|
||||
to: Utc.from_utc_datetime(&m.to),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct ServiceCreateEventRequest {
|
||||
pub name: String,
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
pub user_sub: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct ServiceUpdateEventRequest {
|
||||
pub name: String,
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
pub user_sub: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
pub struct ServiceListEventsQuery {
|
||||
pub user_sub: String,
|
||||
pub upcoming: Option<bool>,
|
||||
}
|
||||
|
||||
fn parse_datetime(dt_str: &str) -> Result<chrono::NaiveDateTime, StatusCode> {
|
||||
if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(dt_str, "%Y-%m-%dT%H:%M") {
|
||||
return Ok(dt);
|
||||
}
|
||||
if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(dt_str, "%Y-%m-%dT%H:%M:%S") {
|
||||
return Ok(dt);
|
||||
}
|
||||
dt_str.parse().map_err(|_| StatusCode::BAD_REQUEST)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/service/v1/events",
|
||||
request_body = ServiceCreateEventRequest,
|
||||
responses(
|
||||
(status = 200, description = "Event created successfully", body = ServiceEventResponse),
|
||||
(status = 400, description = "Invalid request payload"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(
|
||||
("oidc" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn service_create_event(
|
||||
State(state): State<AppState>,
|
||||
_service_account: ServiceAccount,
|
||||
Json(payload): Json<ServiceCreateEventRequest>,
|
||||
) -> Result<Json<ServiceEventResponse>, StatusCode> {
|
||||
let db = state.db;
|
||||
let from = parse_datetime(&payload.from)?;
|
||||
let to = parse_datetime(&payload.to)?;
|
||||
|
||||
let new_event = event::ActiveModel {
|
||||
name: Set(payload.name),
|
||||
from: Set(from),
|
||||
to: Set(to),
|
||||
user_sub: Set(payload.user_sub),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = new_event.insert(&db).await.map_err(|e| {
|
||||
tracing::error!("Service failed to create event: {:?}", e);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
tracing::info!("Service created event {}", result.id);
|
||||
|
||||
Ok(Json(result.into()))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/service/v1/events",
|
||||
params(ServiceListEventsQuery),
|
||||
responses(
|
||||
(status = 200, description = "List of events", body = [ServiceEventResponse]),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(
|
||||
("oidc" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn service_list_events(
|
||||
State(state): State<AppState>,
|
||||
_service_account: ServiceAccount,
|
||||
Query(query): Query<ServiceListEventsQuery>,
|
||||
) -> Result<Json<Vec<ServiceEventResponse>>, StatusCode> {
|
||||
let db = state.db;
|
||||
let mut find = Event::find();
|
||||
|
||||
find = find.filter(event::Column::UserSub.eq(query.user_sub));
|
||||
|
||||
if let Some(true) = query.upcoming {
|
||||
let now = chrono::Utc::now().naive_utc();
|
||||
find = find.filter(event::Column::From.gte(now));
|
||||
}
|
||||
|
||||
let events = find
|
||||
.all(&db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let response = events.into_iter().map(ServiceEventResponse::from).collect();
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/service/v1/events/{id}",
|
||||
params(
|
||||
("id" = i32, Path, description = "Event database id"),
|
||||
ServiceListEventsQuery
|
||||
),
|
||||
request_body = ServiceUpdateEventRequest,
|
||||
responses(
|
||||
(status = 200, description = "Event updated successfully", body = ServiceEventResponse),
|
||||
(status = 400, description = "Invalid request payload"),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 404, description = "Event not found")
|
||||
),
|
||||
security(
|
||||
("oidc" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn service_update_event(
|
||||
State(state): State<AppState>,
|
||||
_service_account: ServiceAccount,
|
||||
Path(event_id): Path<i32>,
|
||||
Json(payload): Json<ServiceUpdateEventRequest>,
|
||||
) -> Result<Json<ServiceEventResponse>, StatusCode> {
|
||||
let db = state.db;
|
||||
let from = parse_datetime(&payload.from)?;
|
||||
let to = parse_datetime(&payload.to)?;
|
||||
|
||||
let result = Event::update_many()
|
||||
.col_expr(event::Column::Name, Expr::value(payload.name.clone()))
|
||||
.col_expr(event::Column::From, Expr::value(from))
|
||||
.col_expr(event::Column::To, Expr::value(to))
|
||||
.filter(event::Column::Id.eq(event_id as i64))
|
||||
.filter(event::Column::UserSub.eq(payload.user_sub.clone()))
|
||||
.exec(&db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Service failed to update event {}: {:?}", event_id, e);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
if result.rows_affected == 0 {
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
Ok(Json(ServiceEventResponse {
|
||||
id: event_id as i64,
|
||||
user_sub: payload.user_sub,
|
||||
name: payload.name,
|
||||
from: Utc.from_utc_datetime(&from),
|
||||
to: Utc.from_utc_datetime(&to),
|
||||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/service/v1/events/{id}",
|
||||
params(
|
||||
("id" = i32, Path, description = "Event database id")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Event deleted successfully"),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 404, description = "Event not found")
|
||||
),
|
||||
security(
|
||||
("oidc" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn service_delete_event(
|
||||
State(state): State<AppState>,
|
||||
_service_account: ServiceAccount,
|
||||
Path(event_id): Path<i32>,
|
||||
) -> Result<StatusCode, StatusCode> {
|
||||
let db = state.db;
|
||||
|
||||
Event::delete_by_id(event_id as i64)
|
||||
.exec(&db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
tracing::info!("Service deleted event {}", event_id);
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ mod config;
|
|||
mod db;
|
||||
mod entities;
|
||||
mod handlers;
|
||||
mod middleware;
|
||||
mod routes;
|
||||
|
||||
use crate::auth::{AppState, OidcClient};
|
||||
|
|
@ -48,9 +47,8 @@ async fn main() {
|
|||
|
||||
// Create router
|
||||
let app = create_router(state)
|
||||
.fallback_service(tower_http::services::ServeDir::new("static"))
|
||||
.layer(axum::middleware::from_fn(middleware::log_errors))
|
||||
.layer(tower_http::cors::CorsLayer::permissive());
|
||||
.layer(tower_http::cors::CorsLayer::permissive())
|
||||
.fallback_service(tower_http::services::ServeDir::new("static"));
|
||||
|
||||
// Start server
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], config.port));
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
use axum::{body::Body, extract::Request, middleware::Next, response::Response};
|
||||
use tracing::error;
|
||||
|
||||
pub async fn log_errors(req: Request<Body>, next: Next) -> Response {
|
||||
let method = req.method().clone();
|
||||
let uri = req.uri().clone();
|
||||
|
||||
let response = next.run(req).await;
|
||||
|
||||
if response.status().is_client_error() || response.status().is_server_error() {
|
||||
error!(
|
||||
method = ?method,
|
||||
uri = ?uri,
|
||||
status = ?response.status(),
|
||||
"Error response returned"
|
||||
);
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
|
@ -2,46 +2,12 @@ use crate::auth::AppState;
|
|||
use crate::handlers::{auth, event};
|
||||
use axum::{
|
||||
Router,
|
||||
routing::{get, post, put},
|
||||
routing::{get, post},
|
||||
};
|
||||
|
||||
use utoipa::OpenApi;
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
paths(
|
||||
crate::handlers::auth::me,
|
||||
crate::handlers::event::create_event,
|
||||
crate::handlers::event::list_events,
|
||||
crate::handlers::event::get_event,
|
||||
crate::handlers::event::update_event,
|
||||
crate::handlers::event::delete_event,
|
||||
crate::handlers::service::service_create_event,
|
||||
crate::handlers::service::service_list_events,
|
||||
crate::handlers::service::service_update_event,
|
||||
crate::handlers::service::service_delete_event,
|
||||
),
|
||||
components(
|
||||
schemas(
|
||||
crate::auth::CurrentUser,
|
||||
crate::entities::user::Model,
|
||||
crate::entities::event::Model,
|
||||
crate::handlers::event::CreateEventRequest,
|
||||
crate::handlers::service::ServiceCreateEventRequest,
|
||||
crate::handlers::service::ServiceEventResponse,
|
||||
)
|
||||
),
|
||||
tags(
|
||||
(name = "calendar", description = "Calendar Management API")
|
||||
)
|
||||
)]
|
||||
struct ApiDoc;
|
||||
|
||||
pub fn create_router(state: AppState) -> Router {
|
||||
// Explicitly set the router state type to AppState
|
||||
Router::<AppState>::new()
|
||||
.merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi()))
|
||||
.route("/health", get(health_check))
|
||||
.route("/auth/login", get(auth::login))
|
||||
.route("/auth/callback", get(auth::callback))
|
||||
|
|
@ -55,17 +21,6 @@ pub fn create_router(state: AppState) -> Router {
|
|||
.put(event::update_event)
|
||||
.delete(event::delete_event),
|
||||
)
|
||||
// Service endpoints
|
||||
.route(
|
||||
"/service/v1/events",
|
||||
post(crate::handlers::service::service_create_event)
|
||||
.get(crate::handlers::service::service_list_events),
|
||||
)
|
||||
.route(
|
||||
"/service/v1/events/{id}",
|
||||
put(crate::handlers::service::service_update_event)
|
||||
.delete(crate::handlers::service::service_delete_event),
|
||||
)
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue