summaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
authorDawid Rycerz <dawid@rycerz.xyz>2025-07-16 23:03:40 +0300
committerDawid Rycerz <dawid@rycerz.xyz>2025-07-16 23:03:40 +0300
commit1aee0b802cad9fc9343b6c2966ba112f9b762f7c (patch)
tree53d9551fbfd3df01ac61ecd1128060a9a9727a84 /src/lib.rs
parentdbb25297da61fe393ca1e8a6b6c6beace2513e0a (diff)
feat: refactor and remove lib usage
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs565
1 files changed, 0 insertions, 565 deletions
diff --git a/src/lib.rs b/src/lib.rs
deleted file mode 100644
index 6f4a795..0000000
--- a/src/lib.rs
+++ /dev/null
@@ -1,565 +0,0 @@
-use axum::Json;
-use axum::http::StatusCode;
-use axum::response::IntoResponse;
-use axum::routing::{post, put};
-use axum::{Router, routing::get};
-use serde_json::json;
-use sqlx::SqlitePool;
-
-// Derive OpenApi for models
-
-mod auth;
-
-use crate::notifications::{NtfySettingsInput, SmtpSettingsInput};
-use crate::weather_thresholds::WeatherThresholdUpdateInput;
-
-fn error_response(status: StatusCode, message: &str) -> axum::response::Response {
- (status, Json(json!({"error": message}))).into_response()
-}
-
-async fn not_found() -> axum::response::Response {
- error_response(StatusCode::NOT_FOUND, "Not Found")
-}
-
-mod users_api {
- use super::*;
- use crate::auth::AuthUser;
- use crate::users::{User, UserRepository, UserRole};
- use axum::{
- Json,
- extract::{Path, State},
- };
- use serde::Deserialize;
- use std::sync::Arc;
-
- #[derive(Deserialize)]
- pub struct CreateUser {
- pub role: Option<UserRole>,
- }
-
- #[derive(Deserialize)]
- pub struct UpdateUser {
- pub role: UserRole,
- }
-
- pub async fn list_users(
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- ) -> Result<Json<Vec<User>>, String> {
- let repo = UserRepository { db: &pool };
- repo.list_users().await.map(Json).map_err(|e| e.to_string())
- }
-
- pub async fn get_user(
- Path(id): Path<i64>,
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- ) -> Result<Json<User>, String> {
- let repo = UserRepository { db: &pool };
- repo.get_user_by_id(id)
- .await
- .map_err(|e| e.to_string())?
- .map(Json)
- .ok_or_else(|| "User not found".to_string())
- }
-
- pub async fn create_user(
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- Json(payload): Json<CreateUser>,
- ) -> Result<Json<User>, String> {
- let repo = UserRepository { db: &pool };
- repo.create_user(None, payload.role)
- .await
- .map(Json)
- .map_err(|e| e.to_string())
- }
-
- pub async fn update_user(
- Path(id): Path<i64>,
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- Json(payload): Json<UpdateUser>,
- ) -> Result<Json<User>, String> {
- let repo = UserRepository { db: &pool };
- repo.update_user(id, payload.role)
- .await
- .map(Json)
- .map_err(|e| e.to_string())
- }
-
- pub async fn delete_user(
- Path(id): Path<i64>,
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- ) -> Result<(), String> {
- let repo = UserRepository { db: &pool };
- repo.delete_user(id).await.map_err(|e| e.to_string())
- }
-}
-
-mod locations_api {
- use super::*;
- use crate::auth::AuthUser;
- use crate::locations::{Location, LocationRepository};
- use axum::{
- Json,
- extract::{Path, State},
- };
- use serde::Deserialize;
- use std::sync::Arc;
-
- #[derive(Deserialize)]
- pub struct CreateLocation {
- pub latitude: f64,
- pub longitude: f64,
- }
-
- #[derive(Deserialize)]
- pub struct UpdateLocation {
- pub latitude: f64,
- pub longitude: f64,
- }
-
- pub async fn list_locations(
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- ) -> Result<Json<Vec<Location>>, String> {
- let repo = LocationRepository { db: &pool };
- repo.list_locations()
- .await
- .map(Json)
- .map_err(|e| e.to_string())
- }
-
- pub async fn get_location(
- Path(id): Path<i64>,
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- ) -> Result<Json<Location>, String> {
- let repo = LocationRepository { db: &pool };
- repo.get_location(id)
- .await
- .map_err(|e| e.to_string())?
- .map(Json)
- .ok_or_else(|| "Location not found".to_string())
- }
-
- pub async fn create_location(
- AuthUser(user): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- Json(payload): Json<CreateLocation>,
- ) -> Result<Json<Location>, String> {
- let repo = LocationRepository { db: &pool };
- repo.create_location(payload.latitude, payload.longitude, user.id)
- .await
- .map(Json)
- .map_err(|e| e.to_string())
- }
-
- pub async fn update_location(
- Path(id): Path<i64>,
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- Json(payload): Json<UpdateLocation>,
- ) -> Result<Json<Location>, String> {
- let repo = LocationRepository { db: &pool };
- // user_id is not updated
- repo.update_location(id, payload.latitude, payload.longitude)
- .await
- .map(Json)
- .map_err(|e| e.to_string())
- }
-
- pub async fn delete_location(
- Path(id): Path<i64>,
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- ) -> Result<(), String> {
- let repo = LocationRepository { db: &pool };
- repo.delete_location(id).await.map_err(|e| e.to_string())
- }
-}
-
-mod thresholds_api {
- use super::*;
- use crate::auth::AuthUser;
- use crate::weather_thresholds::{WeatherThreshold, WeatherThresholdRepository};
- use axum::{
- Json,
- extract::{Path, Query, State},
- };
- use serde::Deserialize;
- use std::sync::Arc;
-
- #[derive(Deserialize)]
- pub struct CreateThreshold {
- pub user_id: i64,
- pub condition_type: String,
- pub threshold_value: f64,
- pub operator: String,
- pub enabled: bool,
- pub description: Option<String>,
- }
-
- #[derive(Deserialize)]
- pub struct UpdateThreshold {
- pub user_id: i64,
- pub condition_type: String,
- pub threshold_value: f64,
- pub operator: String,
- pub enabled: bool,
- pub description: Option<String>,
- }
-
- pub async fn list_thresholds(
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- Query(query): Query<std::collections::HashMap<String, String>>,
- ) -> impl axum::response::IntoResponse {
- let repo = WeatherThresholdRepository { db: &pool };
- let user_id = query
- .get("user_id")
- .and_then(|s| s.parse().ok())
- .ok_or_else(|| "user_id required as query param".to_string())?;
- repo.list_thresholds(user_id)
- .await
- .map(Json)
- .map_err(|e| e.to_string())
- }
-
- pub async fn get_threshold(
- Path((id, user_id)): Path<(i64, i64)>,
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- ) -> Result<Json<WeatherThreshold>, String> {
- let repo = WeatherThresholdRepository { db: &pool };
- repo.get_threshold(id, user_id)
- .await
- .map_err(|e| e.to_string())?
- .map(Json)
- .ok_or_else(|| "Threshold not found".to_string())
- }
-
- pub async fn create_threshold(
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- Json(payload): Json<CreateThreshold>,
- ) -> Result<Json<WeatherThreshold>, String> {
- let repo = WeatherThresholdRepository { db: &pool };
- repo.create_threshold(
- payload.user_id,
- payload.condition_type,
- payload.threshold_value,
- payload.operator,
- payload.enabled,
- payload.description,
- )
- .await
- .map(Json)
- .map_err(|e| e.to_string())
- }
-
- pub async fn update_threshold(
- Path(id): Path<i64>,
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- Json(payload): Json<UpdateThreshold>,
- ) -> Result<Json<WeatherThreshold>, String> {
- let repo = WeatherThresholdRepository { db: &pool };
- repo.update_threshold(WeatherThresholdUpdateInput {
- id,
- user_id: payload.user_id,
- condition_type: payload.condition_type,
- threshold_value: payload.threshold_value,
- operator: payload.operator,
- enabled: payload.enabled,
- description: payload.description,
- })
- .await
- .map(Json)
- .map_err(|e| e.to_string())
- }
-
- pub async fn delete_threshold(
- Path((id, user_id)): Path<(i64, i64)>,
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- ) -> Result<(), String> {
- let repo = WeatherThresholdRepository { db: &pool };
- repo.delete_threshold(id, user_id)
- .await
- .map_err(|e| e.to_string())
- }
-}
-
-mod notifications_api {
- use super::*;
- use crate::auth::AuthUser;
- use crate::notifications::{
- NtfySettings, NtfySettingsRepository, SmtpSettings, SmtpSettingsRepository,
- };
- use axum::{
- Json,
- extract::{Path, State},
- };
- use serde::Deserialize;
- use std::sync::Arc;
-
- // NTFY
- #[derive(Deserialize)]
- pub struct CreateNtfy {
- pub user_id: i64,
- pub enabled: bool,
- pub topic: String,
- pub server_url: String,
- pub priority: i32,
- pub title_template: Option<String>,
- pub message_template: Option<String>,
- }
- #[derive(Deserialize)]
- pub struct UpdateNtfy {
- pub enabled: bool,
- pub topic: String,
- pub server_url: String,
- pub priority: i32,
- pub title_template: Option<String>,
- pub message_template: Option<String>,
- }
- pub async fn get_ntfy_settings(
- Path(user_id): Path<i64>,
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- ) -> Result<Json<NtfySettings>, String> {
- let repo = NtfySettingsRepository { db: &pool };
- repo.get_by_user(user_id)
- .await
- .map_err(|e| e.to_string())?
- .map(Json)
- .ok_or_else(|| "NTFY settings not found".to_string())
- }
- pub async fn create_ntfy_settings(
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- Json(payload): Json<CreateNtfy>,
- ) -> Result<Json<NtfySettings>, String> {
- let repo = NtfySettingsRepository { db: &pool };
- repo.create(NtfySettingsInput {
- user_id: payload.user_id,
- enabled: payload.enabled,
- topic: payload.topic,
- server_url: payload.server_url,
- priority: payload.priority,
- title_template: payload.title_template,
- message_template: payload.message_template,
- })
- .await
- .map(Json)
- .map_err(|e| e.to_string())
- }
- pub async fn update_ntfy_settings(
- Path(id): Path<i64>,
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- Json(payload): Json<UpdateNtfy>,
- ) -> Result<Json<NtfySettings>, String> {
- let repo = NtfySettingsRepository { db: &pool };
- repo.update(
- id,
- NtfySettingsInput {
- user_id: 0, // user_id is not updated here, but struct requires it
- enabled: payload.enabled,
- topic: payload.topic,
- server_url: payload.server_url,
- priority: payload.priority,
- title_template: payload.title_template,
- message_template: payload.message_template,
- },
- )
- .await
- .map(Json)
- .map_err(|e| e.to_string())
- }
- pub async fn delete_ntfy_settings(
- Path(id): Path<i64>,
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- ) -> Result<(), String> {
- let repo = NtfySettingsRepository { db: &pool };
- repo.delete(id).await.map_err(|e| e.to_string())
- }
-
- // SMTP
- #[derive(Deserialize)]
- pub struct CreateSmtp {
- pub user_id: i64,
- pub enabled: bool,
- pub email: String,
- pub smtp_server: String,
- pub smtp_port: i32,
- pub username: Option<String>,
- pub password: Option<String>,
- pub use_tls: bool,
- pub from_email: Option<String>,
- pub from_name: Option<String>,
- pub subject_template: Option<String>,
- pub body_template: Option<String>,
- }
- #[derive(Deserialize)]
- pub struct UpdateSmtp {
- pub enabled: bool,
- pub email: String,
- pub smtp_server: String,
- pub smtp_port: i32,
- pub username: Option<String>,
- pub password: Option<String>,
- pub use_tls: bool,
- pub from_email: Option<String>,
- pub from_name: Option<String>,
- pub subject_template: Option<String>,
- pub body_template: Option<String>,
- }
- pub async fn get_smtp_settings(
- Path(user_id): Path<i64>,
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- ) -> Result<Json<SmtpSettings>, String> {
- let repo = SmtpSettingsRepository { db: &pool };
- repo.get_by_user(user_id)
- .await
- .map_err(|e| e.to_string())?
- .map(Json)
- .ok_or_else(|| "SMTP settings not found".to_string())
- }
- pub async fn create_smtp_settings(
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- Json(payload): Json<CreateSmtp>,
- ) -> Result<Json<SmtpSettings>, String> {
- let repo = SmtpSettingsRepository { db: &pool };
- repo.create(SmtpSettingsInput {
- user_id: payload.user_id,
- enabled: payload.enabled,
- email: payload.email,
- smtp_server: payload.smtp_server,
- smtp_port: payload.smtp_port,
- username: payload.username,
- password: payload.password,
- use_tls: payload.use_tls,
- from_email: payload.from_email,
- from_name: payload.from_name,
- subject_template: payload.subject_template,
- body_template: payload.body_template,
- })
- .await
- .map(Json)
- .map_err(|e| e.to_string())
- }
- pub async fn update_smtp_settings(
- Path(id): Path<i64>,
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- Json(payload): Json<UpdateSmtp>,
- ) -> Result<Json<SmtpSettings>, String> {
- let repo = SmtpSettingsRepository { db: &pool };
- repo.update(
- id,
- SmtpSettingsInput {
- user_id: 0, // user_id is not updated here, but struct requires it
- enabled: payload.enabled,
- email: payload.email,
- smtp_server: payload.smtp_server,
- smtp_port: payload.smtp_port,
- username: payload.username,
- password: payload.password,
- use_tls: payload.use_tls,
- from_email: payload.from_email,
- from_name: payload.from_name,
- subject_template: payload.subject_template,
- body_template: payload.body_template,
- },
- )
- .await
- .map(Json)
- .map_err(|e| e.to_string())
- }
- pub async fn delete_smtp_settings(
- Path(id): Path<i64>,
- AuthUser(_): AuthUser,
- State(pool): State<Arc<SqlitePool>>,
- ) -> Result<(), String> {
- let repo = SmtpSettingsRepository { db: &pool };
- repo.delete(id).await.map_err(|e| e.to_string())
- }
-}
-
-pub fn app_with_state(pool: std::sync::Arc<SqlitePool>) -> Router {
- Router::new()
- .route("/health", get(crate::health::health_handler))
- .nest("/api/users",
- Router::new()
- .route("/", get(users_api::list_users).post(users_api::create_user))
- .route("/{id}", get(users_api::get_user).put(users_api::update_user).delete(users_api::delete_user))
- )
- .nest("/api/locations",
- Router::new()
- .route("/", get(locations_api::list_locations).post(locations_api::create_location))
- .route("/{id}", get(locations_api::get_location).put(locations_api::update_location).delete(locations_api::delete_location))
- )
- .nest("/api/weather-thresholds",
- Router::new()
- .route("/", get(|auth_user, state, query: axum::extract::Query<std::collections::HashMap<String, String>>| async move {
- thresholds_api::list_thresholds(auth_user, state, query).await
- }).post(thresholds_api::create_threshold))
- .route("/{id}/{user_id}", get(thresholds_api::get_threshold).put(thresholds_api::update_threshold).delete(thresholds_api::delete_threshold))
- )
- .nest("/api/ntfy-settings",
- Router::new()
- .route("/user/{user_id}", get(notifications_api::get_ntfy_settings))
- .route("/", post(notifications_api::create_ntfy_settings))
- .route("/{id}", put(notifications_api::update_ntfy_settings).delete(notifications_api::delete_ntfy_settings))
- )
- .nest("/api/smtp-settings",
- Router::new()
- .route("/user/{user_id}", get(notifications_api::get_smtp_settings))
- .route("/", post(notifications_api::create_smtp_settings))
- .route("/{id}", put(notifications_api::update_smtp_settings).delete(notifications_api::delete_smtp_settings))
- )
- .fallback(not_found)
- .with_state(pool)
-}
-
-pub mod health;
-pub mod locations;
-pub mod notifications;
-pub mod users;
-pub mod weather_poller;
-pub mod weather_thresholds;
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use axum::body::Body;
- use axum::body::to_bytes;
- use axum::http::{Request, StatusCode};
- use tower::ServiceExt; // for `oneshot`
-
- #[tokio::test]
- async fn test_health_endpoint() {
- let app = app_with_state(std::sync::Arc::new(
- sqlx::SqlitePool::connect("sqlite::memory:").await.unwrap(),
- ));
- let response = app
- .oneshot(
- Request::builder()
- .uri("/health")
- .body(Body::empty())
- .unwrap(),
- )
- .await
- .unwrap();
- assert_eq!(response.status(), StatusCode::OK);
- let body = to_bytes(response.into_body(), 1024).await.unwrap();
- assert_eq!(&body[..], b"{\"status\":\"ok\"}");
- }
-}