DEVELOPMENT ENVIRONMENT

~liljamo/canwa

9621cd5d4e6923d4fd326a3bf31ffa1bd457a196 — Jonni Liljamo 10 days ago 389d957
feat: separate routes to module
M src/main.rs => src/main.rs +5 -55
@@ 8,17 8,15 @@
use std::sync::Arc;

use axum::{
    Json, Router,
    http::HeaderMap,
    response::{Html, IntoResponse},
    Router,
    response::Html,
    routing::{get, post},
};
use clap::Parser;
use reqwest::StatusCode;
use serde::Deserialize;
use tokio::net::TcpListener;
use tower_http::trace::TraceLayer;
use tracing::{error, info};
use tracing::info;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

mod config;


@@ 26,6 24,7 @@ use config::Config;
mod state;
use state::State;

mod routes;
mod service;

const LICENSE_HTML: &str = include_str!("../static/license.html");


@@ 74,7 73,7 @@ async fn main() {
            "/message",
            post({
                let shared_state = Arc::clone(&state);
                move |headers, body| message(shared_state, headers, body)
                move |headers, body| routes::message::message(shared_state, headers, body)
            }),
        )
        .layer(TraceLayer::new_for_http())


@@ 85,52 84,3 @@ async fn main() {
    let listener = TcpListener::bind(addr).await.unwrap();
    axum::serve(listener, router).await.unwrap();
}

#[derive(Deserialize)]
struct MessageForm {
    title: String,
    message: String,
    #[serde(default)]
    format_commonmark: bool,
}

async fn message(
    state: Arc<State>,
    headers: HeaderMap,
    Json(message): Json<MessageForm>,
) -> impl IntoResponse {
    let token = match headers.get("Authorization") {
        Some(token) => match token.to_str() {
            Ok(token) => token,
            Err(_) => {
                return (StatusCode::UNAUTHORIZED, "unauthorized");
            }
        },
        None => {
            return (StatusCode::UNAUTHORIZED, "unauthorized");
        }
    };

    let notifier = match state.notifiers.iter().find(|(_k, v)| v.token == token) {
        Some(n) => n,
        None => return (StatusCode::UNAUTHORIZED, "unauthorized"),
    };

    info!(msg = "message", notifier = notifier.0);

    for (_k, v) in state
        .services
        .iter()
        .filter(|(k, _v)| notifier.1.services.contains(k))
    {
        match v.send(&message).await {
            Ok(_) => {}
            Err(err) => {
                error!(msg = "message sending failed", ?err);
                return (StatusCode::INTERNAL_SERVER_ERROR, "failed to send message");
            }
        }
    }

    (StatusCode::OK, "")
}

A src/routes/message.rs => src/routes/message.rs +64 -0
@@ 0,0 1,64 @@
/*
 * Copyright (C) 2025 Jonni Liljamo <jonni@liljamo.com>
 *
 * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
 * more information.
 */

use std::sync::Arc;

use axum::{Json, http::HeaderMap, response::IntoResponse};
use reqwest::StatusCode;
use serde::Deserialize;
use tracing::{error, info};

use crate::state::State;

#[derive(Deserialize)]
pub struct MessageForm {
    pub title: String,
    pub message: String,
    #[serde(default)]
    pub format_commonmark: bool,
}

pub async fn message(
    state: Arc<State>,
    headers: HeaderMap,
    Json(message): Json<MessageForm>,
) -> impl IntoResponse {
    let token = match headers.get("Authorization") {
        Some(token) => match token.to_str() {
            Ok(token) => token,
            Err(_) => {
                return (StatusCode::UNAUTHORIZED, "unauthorized");
            }
        },
        None => {
            return (StatusCode::UNAUTHORIZED, "unauthorized");
        }
    };

    let notifier = match state.notifiers.iter().find(|(_k, v)| v.token == token) {
        Some(n) => n,
        None => return (StatusCode::UNAUTHORIZED, "unauthorized"),
    };

    info!(msg = "message", notifier = notifier.0);

    for (_k, v) in state
        .services
        .iter()
        .filter(|(k, _v)| notifier.1.services.contains(k))
    {
        match v.send(&message).await {
            Ok(_) => {}
            Err(err) => {
                error!(msg = "message sending failed", ?err);
                return (StatusCode::INTERNAL_SERVER_ERROR, "failed to send message");
            }
        }
    }

    (StatusCode::OK, "")
}

A src/routes/mod.rs => src/routes/mod.rs +8 -0
@@ 0,0 1,8 @@
/*
 * Copyright (C) 2025 Jonni Liljamo <jonni@liljamo.com>
 *
 * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
 * more information.
 */

pub mod message;

M src/service/email.rs => src/service/email.rs +1 -1
@@ 14,8 14,8 @@ use lettre::{
use serde::{Deserialize, Serialize};

use crate::{
    MessageForm,
    config::{ServiceConfig, deserialize_token},
    routes::message::MessageForm,
};

use super::Service;

M src/service/gotify.rs => src/service/gotify.rs +1 -1
@@ 9,8 9,8 @@ use async_trait::async_trait;
use serde::{Deserialize, Serialize};

use crate::{
    MessageForm,
    config::{ServiceConfig, deserialize_token},
    routes::message::MessageForm,
};

use super::Service;

M src/service/matrix.rs => src/service/matrix.rs +1 -1
@@ 10,8 10,8 @@ use serde::{Deserialize, Serialize};
use serde_json::json;

use crate::{
    MessageForm,
    config::{ServiceConfig, deserialize_token},
    routes::message::MessageForm,
};

use super::Service;

M src/service/mod.rs => src/service/mod.rs +1 -1
@@ 8,7 8,7 @@
use async_trait::async_trait;
use pastey::paste;

use crate::{MessageForm, config::ServiceConfig};
use crate::{config::ServiceConfig, routes::message::MessageForm};

pub mod email;
pub mod gotify;