summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs358
1 files changed, 348 insertions, 10 deletions
diff --git a/src/main.rs b/src/main.rs
index 4cf72ff..924dd81 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,6 +5,8 @@ use axum::routing::{post, put};
use axum::{Router, routing::get};
use serde_json::json;
use sqlx::SqlitePool;
+use utoipa::OpenApi;
+use utoipa_swagger_ui::SwaggerUi;
mod auth;
mod health;
@@ -26,16 +28,25 @@ mod users_api {
use serde::Deserialize;
use std::sync::Arc;
- #[derive(Deserialize)]
+ #[derive(Deserialize, utoipa::ToSchema)]
pub struct CreateUser {
pub role: Option<UserRole>,
}
- #[derive(Deserialize)]
+ #[derive(Deserialize, utoipa::ToSchema)]
pub struct UpdateUser {
pub role: UserRole,
}
+ #[utoipa::path(
+ get,
+ path = "/api/users",
+ responses(
+ (status = 200, description = "List users", body = [User]),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "users"
+ )]
pub async fn list_users(
AuthUser(_): AuthUser,
State(pool): State<Arc<SqlitePool>>,
@@ -44,6 +55,19 @@ mod users_api {
repo.list_users().await.map(Json).map_err(|e| e.to_string())
}
+ #[utoipa::path(
+ get,
+ path = "/api/users/{id}",
+ params(
+ ("id" = i64, Path, description = "User ID")
+ ),
+ responses(
+ (status = 200, description = "Get user by ID", body = User),
+ (status = 404, description = "User not found"),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "users"
+ )]
pub async fn get_user(
Path(id): Path<i64>,
AuthUser(_): AuthUser,
@@ -57,6 +81,16 @@ mod users_api {
.ok_or_else(|| "User not found".to_string())
}
+ #[utoipa::path(
+ post,
+ path = "/api/users",
+ request_body = CreateUser,
+ responses(
+ (status = 200, description = "Create user", body = User),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "users"
+ )]
pub async fn create_user(
AuthUser(_): AuthUser,
State(pool): State<Arc<SqlitePool>>,
@@ -69,6 +103,20 @@ mod users_api {
.map_err(|e| e.to_string())
}
+ #[utoipa::path(
+ put,
+ path = "/api/users/{id}",
+ params(
+ ("id" = i64, Path, description = "User ID")
+ ),
+ request_body = UpdateUser,
+ responses(
+ (status = 200, description = "Update user", body = User),
+ (status = 404, description = "User not found"),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "users"
+ )]
pub async fn update_user(
Path(id): Path<i64>,
AuthUser(_): AuthUser,
@@ -82,6 +130,19 @@ mod users_api {
.map_err(|e| e.to_string())
}
+ #[utoipa::path(
+ delete,
+ path = "/api/users/{id}",
+ params(
+ ("id" = i64, Path, description = "User ID")
+ ),
+ responses(
+ (status = 200, description = "User deleted"),
+ (status = 404, description = "User not found"),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "users"
+ )]
pub async fn delete_user(
Path(id): Path<i64>,
AuthUser(_): AuthUser,
@@ -103,18 +164,27 @@ mod locations_api {
use serde::Deserialize;
use std::sync::Arc;
- #[derive(Deserialize)]
+ #[derive(Deserialize, utoipa::ToSchema)]
pub struct CreateLocation {
pub latitude: f64,
pub longitude: f64,
}
- #[derive(Deserialize)]
+ #[derive(Deserialize, utoipa::ToSchema)]
pub struct UpdateLocation {
pub latitude: f64,
pub longitude: f64,
}
+ #[utoipa::path(
+ get,
+ path = "/api/locations",
+ responses(
+ (status = 200, description = "List locations", body = [Location]),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "locations"
+ )]
pub async fn list_locations(
AuthUser(_): AuthUser,
State(pool): State<Arc<SqlitePool>>,
@@ -126,6 +196,19 @@ mod locations_api {
.map_err(|e| e.to_string())
}
+ #[utoipa::path(
+ get,
+ path = "/api/locations/{id}",
+ params(
+ ("id" = i64, Path, description = "Location ID")
+ ),
+ responses(
+ (status = 200, description = "Get location by ID", body = Location),
+ (status = 404, description = "Location not found"),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "locations"
+ )]
pub async fn get_location(
Path(id): Path<i64>,
AuthUser(_): AuthUser,
@@ -139,6 +222,16 @@ mod locations_api {
.ok_or_else(|| "Location not found".to_string())
}
+ #[utoipa::path(
+ post,
+ path = "/api/locations",
+ request_body = CreateLocation,
+ responses(
+ (status = 200, description = "Create location", body = Location),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "locations"
+ )]
pub async fn create_location(
AuthUser(user): AuthUser,
State(pool): State<Arc<SqlitePool>>,
@@ -151,6 +244,20 @@ mod locations_api {
.map_err(|e| e.to_string())
}
+ #[utoipa::path(
+ put,
+ path = "/api/locations/{id}",
+ params(
+ ("id" = i64, Path, description = "Location ID")
+ ),
+ request_body = UpdateLocation,
+ responses(
+ (status = 200, description = "Update location", body = Location),
+ (status = 404, description = "Location not found"),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "locations"
+ )]
pub async fn update_location(
Path(id): Path<i64>,
AuthUser(_): AuthUser,
@@ -164,6 +271,19 @@ mod locations_api {
.map_err(|e| e.to_string())
}
+ #[utoipa::path(
+ delete,
+ path = "/api/locations/{id}",
+ params(
+ ("id" = i64, Path, description = "Location ID")
+ ),
+ responses(
+ (status = 200, description = "Location deleted"),
+ (status = 404, description = "Location not found"),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "locations"
+ )]
pub async fn delete_location(
Path(id): Path<i64>,
AuthUser(_): AuthUser,
@@ -185,7 +305,7 @@ mod thresholds_api {
use serde::Deserialize;
use std::sync::Arc;
- #[derive(Deserialize)]
+ #[derive(Deserialize, utoipa::ToSchema)]
pub struct CreateThreshold {
pub condition_type: String,
pub threshold_value: f64,
@@ -194,7 +314,7 @@ mod thresholds_api {
pub description: Option<String>,
}
- #[derive(Deserialize)]
+ #[derive(Deserialize, utoipa::ToSchema)]
pub struct UpdateThreshold {
pub condition_type: String,
pub threshold_value: f64,
@@ -203,6 +323,15 @@ mod thresholds_api {
pub description: Option<String>,
}
+ #[utoipa::path(
+ get,
+ path = "/api/weather-thresholds",
+ responses(
+ (status = 200, description = "List weather thresholds", body = [WeatherThreshold]),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "weather-thresholds"
+ )]
pub async fn list_thresholds(
AuthUser(user): AuthUser,
State(pool): State<Arc<SqlitePool>>,
@@ -214,6 +343,19 @@ mod thresholds_api {
}
}
+ #[utoipa::path(
+ get,
+ path = "/api/weather-thresholds/{id}",
+ params(
+ ("id" = i64, Path, description = "Threshold ID")
+ ),
+ responses(
+ (status = 200, description = "Get weather threshold by ID", body = WeatherThreshold),
+ (status = 404, description = "Threshold not found"),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "weather-thresholds"
+ )]
pub async fn get_threshold(
Path(id): Path<i64>,
AuthUser(user): AuthUser,
@@ -227,6 +369,16 @@ mod thresholds_api {
.ok_or_else(|| "Threshold not found".to_string())
}
+ #[utoipa::path(
+ post,
+ path = "/api/weather-thresholds",
+ request_body = CreateThreshold,
+ responses(
+ (status = 200, description = "Create weather threshold", body = WeatherThreshold),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "weather-thresholds"
+ )]
pub async fn create_threshold(
AuthUser(user): AuthUser,
State(pool): State<Arc<SqlitePool>>,
@@ -246,6 +398,20 @@ mod thresholds_api {
.map_err(|e| e.to_string())
}
+ #[utoipa::path(
+ put,
+ path = "/api/weather-thresholds/{id}",
+ params(
+ ("id" = i64, Path, description = "Threshold ID")
+ ),
+ request_body = UpdateThreshold,
+ responses(
+ (status = 200, description = "Update weather threshold", body = WeatherThreshold),
+ (status = 404, description = "Threshold not found"),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "weather-thresholds"
+ )]
pub async fn update_threshold(
Path(id): Path<i64>,
AuthUser(user): AuthUser,
@@ -267,6 +433,19 @@ mod thresholds_api {
.map_err(|e| e.to_string())
}
+ #[utoipa::path(
+ delete,
+ path = "/api/weather-thresholds/{id}",
+ params(
+ ("id" = i64, Path, description = "Threshold ID")
+ ),
+ responses(
+ (status = 200, description = "Threshold deleted"),
+ (status = 404, description = "Threshold not found"),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "weather-thresholds"
+ )]
pub async fn delete_threshold(
Path(id): Path<i64>,
AuthUser(user): AuthUser,
@@ -293,7 +472,7 @@ mod notifications_api {
use std::sync::Arc;
// NTFY
- #[derive(Deserialize)]
+ #[derive(Deserialize, utoipa::ToSchema)]
pub struct CreateNtfy {
pub enabled: bool,
pub topic: String,
@@ -302,7 +481,7 @@ mod notifications_api {
pub title_template: Option<String>,
pub message_template: Option<String>,
}
- #[derive(Deserialize)]
+ #[derive(Deserialize, utoipa::ToSchema)]
pub struct UpdateNtfy {
pub enabled: bool,
pub topic: String,
@@ -311,6 +490,16 @@ mod notifications_api {
pub title_template: Option<String>,
pub message_template: Option<String>,
}
+ #[utoipa::path(
+ get,
+ path = "/api/ntfy-settings/me",
+ responses(
+ (status = 200, description = "Get NTFY settings for current user", body = NtfySettings),
+ (status = 404, description = "NTFY settings not found"),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "notifications"
+ )]
pub async fn get_ntfy_settings(
AuthUser(user): AuthUser,
State(pool): State<Arc<SqlitePool>>,
@@ -322,6 +511,17 @@ mod notifications_api {
.map(Json)
.ok_or_else(|| "NTFY settings not found".to_string())
}
+
+ #[utoipa::path(
+ post,
+ path = "/api/ntfy-settings",
+ request_body = CreateNtfy,
+ responses(
+ (status = 200, description = "Create NTFY settings", body = NtfySettings),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "notifications"
+ )]
pub async fn create_ntfy_settings(
AuthUser(user): AuthUser,
State(pool): State<Arc<SqlitePool>>,
@@ -341,6 +541,21 @@ mod notifications_api {
.map(Json)
.map_err(|e| e.to_string())
}
+
+ #[utoipa::path(
+ put,
+ path = "/api/ntfy-settings/{id}",
+ params(
+ ("id" = i64, Path, description = "NTFY settings ID")
+ ),
+ request_body = UpdateNtfy,
+ responses(
+ (status = 200, description = "Update NTFY settings", body = NtfySettings),
+ (status = 404, description = "NTFY settings not found"),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "notifications"
+ )]
pub async fn update_ntfy_settings(
Path(id): Path<i64>,
AuthUser(user): AuthUser,
@@ -364,6 +579,20 @@ mod notifications_api {
.map(Json)
.map_err(|e| e.to_string())
}
+
+ #[utoipa::path(
+ delete,
+ path = "/api/ntfy-settings/{id}",
+ params(
+ ("id" = i64, Path, description = "NTFY settings ID")
+ ),
+ responses(
+ (status = 200, description = "NTFY settings deleted"),
+ (status = 404, description = "NTFY settings not found"),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "notifications"
+ )]
pub async fn delete_ntfy_settings(
Path(id): Path<i64>,
AuthUser(_): AuthUser,
@@ -374,7 +603,7 @@ mod notifications_api {
}
// SMTP
- #[derive(Deserialize)]
+ #[derive(Deserialize, utoipa::ToSchema)]
pub struct CreateSmtp {
pub enabled: bool,
pub email: String,
@@ -388,7 +617,7 @@ mod notifications_api {
pub subject_template: Option<String>,
pub body_template: Option<String>,
}
- #[derive(Deserialize)]
+ #[derive(Deserialize, utoipa::ToSchema)]
pub struct UpdateSmtp {
pub enabled: bool,
pub email: String,
@@ -402,6 +631,16 @@ mod notifications_api {
pub subject_template: Option<String>,
pub body_template: Option<String>,
}
+ #[utoipa::path(
+ get,
+ path = "/api/smtp-settings/me",
+ responses(
+ (status = 200, description = "Get SMTP settings for current user", body = SmtpSettings),
+ (status = 404, description = "SMTP settings not found"),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "notifications"
+ )]
pub async fn get_smtp_settings(
AuthUser(user): AuthUser,
State(pool): State<Arc<SqlitePool>>,
@@ -413,6 +652,17 @@ mod notifications_api {
.map(Json)
.ok_or_else(|| "SMTP settings not found".to_string())
}
+
+ #[utoipa::path(
+ post,
+ path = "/api/smtp-settings",
+ request_body = CreateSmtp,
+ responses(
+ (status = 200, description = "Create SMTP settings", body = SmtpSettings),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "notifications"
+ )]
pub async fn create_smtp_settings(
AuthUser(user): AuthUser,
State(pool): State<Arc<SqlitePool>>,
@@ -437,6 +687,21 @@ mod notifications_api {
.map(Json)
.map_err(|e| e.to_string())
}
+
+ #[utoipa::path(
+ put,
+ path = "/api/smtp-settings/{id}",
+ params(
+ ("id" = i64, Path, description = "SMTP settings ID")
+ ),
+ request_body = UpdateSmtp,
+ responses(
+ (status = 200, description = "Update SMTP settings", body = SmtpSettings),
+ (status = 404, description = "SMTP settings not found"),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "notifications"
+ )]
pub async fn update_smtp_settings(
Path(id): Path<i64>,
AuthUser(user): AuthUser,
@@ -465,6 +730,20 @@ mod notifications_api {
.map(Json)
.map_err(|e| e.to_string())
}
+
+ #[utoipa::path(
+ delete,
+ path = "/api/smtp-settings/{id}",
+ params(
+ ("id" = i64, Path, description = "SMTP settings ID")
+ ),
+ responses(
+ (status = 200, description = "SMTP settings deleted"),
+ (status = 404, description = "SMTP settings not found"),
+ (status = 401, description = "Unauthorized")
+ ),
+ tag = "notifications"
+ )]
pub async fn delete_smtp_settings(
Path(id): Path<i64>,
AuthUser(_): AuthUser,
@@ -483,9 +762,68 @@ pub async fn not_found() -> axum::response::Response {
error_response(StatusCode::NOT_FOUND, "Not Found")
}
+#[derive(OpenApi)]
+#[openapi(
+ paths(
+ users_api::list_users,
+ users_api::get_user,
+ users_api::create_user,
+ users_api::update_user,
+ users_api::delete_user,
+ locations_api::list_locations,
+ locations_api::get_location,
+ locations_api::create_location,
+ locations_api::update_location,
+ locations_api::delete_location,
+ thresholds_api::list_thresholds,
+ thresholds_api::get_threshold,
+ thresholds_api::create_threshold,
+ thresholds_api::update_threshold,
+ thresholds_api::delete_threshold,
+ notifications_api::get_ntfy_settings,
+ notifications_api::create_ntfy_settings,
+ notifications_api::update_ntfy_settings,
+ notifications_api::delete_ntfy_settings,
+ notifications_api::get_smtp_settings,
+ notifications_api::create_smtp_settings,
+ notifications_api::update_smtp_settings,
+ notifications_api::delete_smtp_settings,
+ health::health_handler,
+ ),
+ components(
+ schemas(
+ users::User,
+ users::UserRole,
+ users_api::CreateUser,
+ users_api::UpdateUser,
+ locations::Location,
+ locations_api::CreateLocation,
+ locations_api::UpdateLocation,
+ weather_thresholds::WeatherThreshold,
+ thresholds_api::CreateThreshold,
+ thresholds_api::UpdateThreshold,
+ notifications::NtfySettings,
+ notifications::SmtpSettings,
+ notifications_api::CreateNtfy,
+ notifications_api::UpdateNtfy,
+ notifications_api::CreateSmtp,
+ notifications_api::UpdateSmtp,
+ )
+ ),
+ tags(
+ (name = "users", description = "User management endpoints"),
+ (name = "locations", description = "Location management endpoints"),
+ (name = "weather-thresholds", description = "Weather threshold endpoints"),
+ (name = "notifications", description = "Notification settings endpoints"),
+ (name = "health", description = "Health check endpoint"),
+ )
+)]
+pub struct ApiDoc;
+
pub fn app_with_state(pool: std::sync::Arc<SqlitePool>) -> Router {
Router::new()
.route("/health", get(health::health_handler))
+ .merge(SwaggerUi::new("/swagger-ui").url("/openapi.json", ApiDoc::openapi()))
.nest(
"/api/users",
Router::new()