atagen

malice

public
Star0
Watch0
e2b04d2f1db0421a488ad56ba2b03235eb172a76
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git

Blame: crates/malice-server/src/routes.rs

973f8fbdaf80atagen2026-09-28 09:10 UTC1
use std::sync::Arc;
973f8fbdaf80atagen2026-09-28 09:10 UTC2
973f8fbdaf80atagen2026-09-28 09:10 UTC3
use axum::{
973f8fbdaf80atagen2026-09-28 09:10 UTC4
    Router,
973f8fbdaf80atagen2026-09-28 09:10 UTC5
    extract::{DefaultBodyLimit, Multipart, Query, State},
dffd63b58af4atagen2026-09-28 09:10 UTC6
    http::{HeaderMap, StatusCode},
973f8fbdaf80atagen2026-09-28 09:10 UTC7
    middleware,
973f8fbdaf80atagen2026-09-28 09:10 UTC8
    response::{IntoResponse, Response, Sse, sse::Event as SseEvent},
dffd63b58af4atagen2026-09-28 09:10 UTC9
    routing::{get, post},
973f8fbdaf80atagen2026-09-28 09:10 UTC10
};
973f8fbdaf80atagen2026-09-28 09:10 UTC11
use futures_util::StreamExt;
973f8fbdaf80atagen2026-09-28 09:10 UTC12
use macltok::Retain;
973f8fbdaf80atagen2026-09-28 09:10 UTC13
use malice_core::{
973f8fbdaf80atagen2026-09-28 09:10 UTC14
    feed::subscribe,
544a170ead2catagen2026-10-20 11:47 UTC15
    login::LoginInput,
7cca1d5628d1atagen2026-10-24 07:36 UTC16
    request::{Reply, Request, SendKind, dispatch},
973f8fbdaf80atagen2026-09-28 09:10 UTC17
};
973f8fbdaf80atagen2026-09-28 09:10 UTC18
use serde::{Deserialize, Serialize};
973f8fbdaf80atagen2026-09-28 09:10 UTC19
use tower_http::{
973f8fbdaf80atagen2026-09-28 09:10 UTC20
    services::{ServeDir, ServeFile},
973f8fbdaf80atagen2026-09-28 09:10 UTC21
    trace::TraceLayer,
973f8fbdaf80atagen2026-09-28 09:10 UTC22
};
973f8fbdaf80atagen2026-09-28 09:10 UTC23
973f8fbdaf80atagen2026-09-28 09:10 UTC24
use crate::{
973f8fbdaf80atagen2026-09-28 09:10 UTC25
    csrf::{csrf_guard, session_sid, set_cookie_value},
973f8fbdaf80atagen2026-09-28 09:10 UTC26
    error::{ApiError, json_ok, no_content},
973f8fbdaf80atagen2026-09-28 09:10 UTC27
    state::{AppState, RestoreError, UserSession},
973f8fbdaf80atagen2026-09-28 09:10 UTC28
};
973f8fbdaf80atagen2026-09-28 09:10 UTC29
973f8fbdaf80atagen2026-09-28 09:10 UTC30
#[derive(Debug, Deserialize)]
973f8fbdaf80atagen2026-09-28 09:10 UTC31
struct RoomIdBody {
973f8fbdaf80atagen2026-09-28 09:10 UTC32
    #[serde(default)]
973f8fbdaf80atagen2026-09-28 09:10 UTC33
    room_id: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC34
}
973f8fbdaf80atagen2026-09-28 09:10 UTC35
973f8fbdaf80atagen2026-09-28 09:10 UTC36
#[derive(Debug, Deserialize)]
973f8fbdaf80atagen2026-09-28 09:10 UTC37
struct SendBody {
973f8fbdaf80atagen2026-09-28 09:10 UTC38
    #[serde(default)]
973f8fbdaf80atagen2026-09-28 09:10 UTC39
    room_id: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC40
    #[serde(default)]
973f8fbdaf80atagen2026-09-28 09:10 UTC41
    body: String,
df3c0f2b3da4atagen2026-10-22 13:07 UTC42
    #[serde(default)]
df3c0f2b3da4atagen2026-10-22 13:07 UTC43
    formatted_body: Option<String>,
df3c0f2b3da4atagen2026-10-22 13:07 UTC44
    #[serde(default)]
df3c0f2b3da4atagen2026-10-22 13:07 UTC45
    reply_to: Option<String>,
cf23dbf4e77catagen2026-10-23 06:36 UTC46
    #[serde(default)]
cf23dbf4e77catagen2026-10-23 06:36 UTC47
    mentions: Vec<String>,
e4c64c36f105atagen2026-10-24 07:09 UTC48
    #[serde(default)]
7cca1d5628d1atagen2026-10-24 07:36 UTC49
    kind: SendKind,
973f8fbdaf80atagen2026-09-28 09:10 UTC50
}
973f8fbdaf80atagen2026-09-28 09:10 UTC51
973f8fbdaf80atagen2026-09-28 09:10 UTC52
#[derive(Debug, Deserialize)]
973f8fbdaf80atagen2026-09-28 09:10 UTC53
struct PaginateBody {
973f8fbdaf80atagen2026-09-28 09:10 UTC54
    #[serde(default)]
973f8fbdaf80atagen2026-09-28 09:10 UTC55
    room_id: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC56
}
973f8fbdaf80atagen2026-09-28 09:10 UTC57
973f8fbdaf80atagen2026-09-28 09:10 UTC58
#[derive(Debug, Deserialize)]
824dfd38cdf3atagen2026-10-24 13:43 UTC59
struct EventContextBody {
824dfd38cdf3atagen2026-10-24 13:43 UTC60
    #[serde(default)]
824dfd38cdf3atagen2026-10-24 13:43 UTC61
    room_id: String,
824dfd38cdf3atagen2026-10-24 13:43 UTC62
    #[serde(default)]
824dfd38cdf3atagen2026-10-24 13:43 UTC63
    event_id: String,
824dfd38cdf3atagen2026-10-24 13:43 UTC64
    #[serde(default)]
824dfd38cdf3atagen2026-10-24 13:43 UTC65
    before_limit: u32,
824dfd38cdf3atagen2026-10-24 13:43 UTC66
    #[serde(default)]
824dfd38cdf3atagen2026-10-24 13:43 UTC67
    after_limit: u32,
824dfd38cdf3atagen2026-10-24 13:43 UTC68
}
824dfd38cdf3atagen2026-10-24 13:43 UTC69
824dfd38cdf3atagen2026-10-24 13:43 UTC70
#[derive(Debug, Deserialize)]
824dfd38cdf3atagen2026-10-24 13:43 UTC71
struct EventContextPageBody {
824dfd38cdf3atagen2026-10-24 13:43 UTC72
    #[serde(default)]
824dfd38cdf3atagen2026-10-24 13:43 UTC73
    room_id: String,
824dfd38cdf3atagen2026-10-24 13:43 UTC74
    #[serde(default)]
824dfd38cdf3atagen2026-10-24 13:43 UTC75
    token: String,
824dfd38cdf3atagen2026-10-24 13:43 UTC76
    #[serde(default)]
824dfd38cdf3atagen2026-10-24 13:43 UTC77
    limit: u32,
824dfd38cdf3atagen2026-10-24 13:43 UTC78
}
824dfd38cdf3atagen2026-10-24 13:43 UTC79
824dfd38cdf3atagen2026-10-24 13:43 UTC80
#[derive(Debug, Deserialize)]
973f8fbdaf80atagen2026-09-28 09:10 UTC81
struct JoinBody {
973f8fbdaf80atagen2026-09-28 09:10 UTC82
    #[serde(default)]
973f8fbdaf80atagen2026-09-28 09:10 UTC83
    room: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC84
}
973f8fbdaf80atagen2026-09-28 09:10 UTC85
973f8fbdaf80atagen2026-09-28 09:10 UTC86
#[derive(Debug, Deserialize)]
973f8fbdaf80atagen2026-09-28 09:10 UTC87
struct CreateBody {
973f8fbdaf80atagen2026-09-28 09:10 UTC88
    #[serde(default)]
973f8fbdaf80atagen2026-09-28 09:10 UTC89
    name: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC90
    #[serde(default)]
973f8fbdaf80atagen2026-09-28 09:10 UTC91
    topic: Option<String>,
973f8fbdaf80atagen2026-09-28 09:10 UTC92
}
973f8fbdaf80atagen2026-09-28 09:10 UTC93
973f8fbdaf80atagen2026-09-28 09:10 UTC94
#[derive(Debug, Deserialize)]
973f8fbdaf80atagen2026-09-28 09:10 UTC95
struct ReactBody {
973f8fbdaf80atagen2026-09-28 09:10 UTC96
    #[serde(default)]
973f8fbdaf80atagen2026-09-28 09:10 UTC97
    room_id: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC98
    #[serde(default)]
973f8fbdaf80atagen2026-09-28 09:10 UTC99
    event_id: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC100
    #[serde(default)]
973f8fbdaf80atagen2026-09-28 09:10 UTC101
    key: String,
ecb8598b38afatagen2026-10-22 09:19 UTC102
    #[serde(default)]
ecb8598b38afatagen2026-10-22 09:19 UTC103
    shortcode: Option<String>,
973f8fbdaf80atagen2026-09-28 09:10 UTC104
}
973f8fbdaf80atagen2026-09-28 09:10 UTC105
973f8fbdaf80atagen2026-09-28 09:10 UTC106
#[derive(Debug, Deserialize)]
cf23dbf4e77catagen2026-10-23 06:36 UTC107
struct EditBody {
cf23dbf4e77catagen2026-10-23 06:36 UTC108
    #[serde(default)]
cf23dbf4e77catagen2026-10-23 06:36 UTC109
    room_id: String,
cf23dbf4e77catagen2026-10-23 06:36 UTC110
    #[serde(default)]
cf23dbf4e77catagen2026-10-23 06:36 UTC111
    event_id: String,
cf23dbf4e77catagen2026-10-23 06:36 UTC112
    #[serde(default)]
cf23dbf4e77catagen2026-10-23 06:36 UTC113
    body: String,
cf23dbf4e77catagen2026-10-23 06:36 UTC114
    #[serde(default)]
cf23dbf4e77catagen2026-10-23 06:36 UTC115
    formatted_body: Option<String>,
cf23dbf4e77catagen2026-10-23 06:36 UTC116
    #[serde(default)]
cf23dbf4e77catagen2026-10-23 06:36 UTC117
    mentions: Vec<String>,
cf23dbf4e77catagen2026-10-23 06:36 UTC118
}
cf23dbf4e77catagen2026-10-23 06:36 UTC119
cf23dbf4e77catagen2026-10-23 06:36 UTC120
#[derive(Debug, Deserialize)]
973f8fbdaf80atagen2026-09-28 09:10 UTC121
struct ReadBody {
973f8fbdaf80atagen2026-09-28 09:10 UTC122
    #[serde(default)]
973f8fbdaf80atagen2026-09-28 09:10 UTC123
    room_id: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC124
    #[serde(default)]
973f8fbdaf80atagen2026-09-28 09:10 UTC125
    event_id: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC126
}
973f8fbdaf80atagen2026-09-28 09:10 UTC127
973f8fbdaf80atagen2026-09-28 09:10 UTC128
#[derive(Debug, Deserialize)]
973f8fbdaf80atagen2026-09-28 09:10 UTC129
struct MembersQuery {
973f8fbdaf80atagen2026-09-28 09:10 UTC130
    #[serde(default)]
973f8fbdaf80atagen2026-09-28 09:10 UTC131
    room_id: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC132
}
973f8fbdaf80atagen2026-09-28 09:10 UTC133
#[derive(Debug, Deserialize)]
973f8fbdaf80atagen2026-09-28 09:10 UTC134
struct RecoverBody {
973f8fbdaf80atagen2026-09-28 09:10 UTC135
    recovery_key: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC136
}
973f8fbdaf80atagen2026-09-28 09:10 UTC137
973f8fbdaf80atagen2026-09-28 09:10 UTC138
#[derive(Debug, Deserialize)]
973f8fbdaf80atagen2026-09-28 09:10 UTC139
struct VerifyBody {
973f8fbdaf80atagen2026-09-28 09:10 UTC140
    action: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC141
}
973f8fbdaf80atagen2026-09-28 09:10 UTC142
973f8fbdaf80atagen2026-09-28 09:10 UTC143
#[derive(Debug, Deserialize)]
973f8fbdaf80atagen2026-09-28 09:10 UTC144
struct MediaQuery {
973f8fbdaf80atagen2026-09-28 09:10 UTC145
    mxc: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC146
}
973f8fbdaf80atagen2026-09-28 09:10 UTC147
#[derive(Debug, Deserialize)]
973f8fbdaf80atagen2026-09-28 09:10 UTC148
struct MessageMediaQuery {
973f8fbdaf80atagen2026-09-28 09:10 UTC149
    media_id: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC150
    size: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC151
}
973f8fbdaf80atagen2026-09-28 09:10 UTC152
973f8fbdaf80atagen2026-09-28 09:10 UTC153
#[derive(Debug, Deserialize)]
973f8fbdaf80atagen2026-09-28 09:10 UTC154
struct TypingBody {
973f8fbdaf80atagen2026-09-28 09:10 UTC155
    #[serde(default)]
973f8fbdaf80atagen2026-09-28 09:10 UTC156
    room_id: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC157
    #[serde(default)]
973f8fbdaf80atagen2026-09-28 09:10 UTC158
    typing: bool,
973f8fbdaf80atagen2026-09-28 09:10 UTC159
}
973f8fbdaf80atagen2026-09-28 09:10 UTC160
544a170ead2catagen2026-10-20 11:47 UTC161
#[derive(Debug, Deserialize)]
544a170ead2catagen2026-10-20 11:47 UTC162
struct AccountDataQuery {
544a170ead2catagen2026-10-20 11:47 UTC163
    event_type: String,
544a170ead2catagen2026-10-20 11:47 UTC164
}
544a170ead2catagen2026-10-20 11:47 UTC165
544a170ead2catagen2026-10-20 11:47 UTC166
#[derive(Debug, Deserialize)]
ecb8598b38afatagen2026-10-22 09:19 UTC167
struct UrlPreviewQuery {
ecb8598b38afatagen2026-10-22 09:19 UTC168
    #[serde(default)]
ecb8598b38afatagen2026-10-22 09:19 UTC169
    room_id: String,
ecb8598b38afatagen2026-10-22 09:19 UTC170
    #[serde(default)]
ecb8598b38afatagen2026-10-22 09:19 UTC171
    url: String,
ecb8598b38afatagen2026-10-22 09:19 UTC172
}
ecb8598b38afatagen2026-10-22 09:19 UTC173
ecb8598b38afatagen2026-10-22 09:19 UTC174
#[derive(Debug, Deserialize)]
544a170ead2catagen2026-10-20 11:47 UTC175
struct AccountDataBody {
544a170ead2catagen2026-10-20 11:47 UTC176
    event_type: String,
544a170ead2catagen2026-10-20 11:47 UTC177
    content: serde_json::Value,
544a170ead2catagen2026-10-20 11:47 UTC178
}
544a170ead2catagen2026-10-20 11:47 UTC179
973f8fbdaf80atagen2026-09-28 09:10 UTC180
#[derive(Debug, Serialize)]
973f8fbdaf80atagen2026-09-28 09:10 UTC181
struct SoftLogoutInfo {
973f8fbdaf80atagen2026-09-28 09:10 UTC182
    user_id: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC183
    homeserver: String,
973f8fbdaf80atagen2026-09-28 09:10 UTC184
}
973f8fbdaf80atagen2026-09-28 09:10 UTC185
973f8fbdaf80atagen2026-09-28 09:10 UTC186
#[derive(Debug, Serialize)]
973f8fbdaf80atagen2026-09-28 09:10 UTC187
struct SessionResponse {
973f8fbdaf80atagen2026-09-28 09:10 UTC188
    user_id: Option<String>,
973f8fbdaf80atagen2026-09-28 09:10 UTC189
    #[serde(skip_serializing_if = "Option::is_none")]
973f8fbdaf80atagen2026-09-28 09:10 UTC190
    soft_logout: Option<SoftLogoutInfo>,
973f8fbdaf80atagen2026-09-28 09:10 UTC191
}
973f8fbdaf80atagen2026-09-28 09:10 UTC192
973f8fbdaf80atagen2026-09-28 09:10 UTC193
async fn auth(
973f8fbdaf80atagen2026-09-28 09:10 UTC194
    state: &AppState,
973f8fbdaf80atagen2026-09-28 09:10 UTC195
    headers: &HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC196
) -> Result<(Arc<UserSession>, String), ApiError> {
973f8fbdaf80atagen2026-09-28 09:10 UTC197
    let Some(sid) = session_sid(headers) else {
973f8fbdaf80atagen2026-09-28 09:10 UTC198
        return Err(ApiError::unauthorized("not signed in"));
973f8fbdaf80atagen2026-09-28 09:10 UTC199
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC200
    match state.get_or_restore(&sid).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC201
        Ok(user) => Ok((user, sid)),
973f8fbdaf80atagen2026-09-28 09:10 UTC202
        Err(RestoreError::NotFound) => Err(ApiError::unauthorized_stale(
973f8fbdaf80atagen2026-09-28 09:10 UTC203
            "session expired; sign in again",
973f8fbdaf80atagen2026-09-28 09:10 UTC204
            state.cookie_secure(),
973f8fbdaf80atagen2026-09-28 09:10 UTC205
        )),
973f8fbdaf80atagen2026-09-28 09:10 UTC206
        Err(RestoreError::SoftLoggedOut { .. }) => Err(ApiError::unauthorized(
973f8fbdaf80atagen2026-09-28 09:10 UTC207
            "signed out by the server, sign in again",
973f8fbdaf80atagen2026-09-28 09:10 UTC208
        )),
973f8fbdaf80atagen2026-09-28 09:10 UTC209
        Err(RestoreError::Stale(message)) => {
973f8fbdaf80atagen2026-09-28 09:10 UTC210
            state.remove_session(&sid, Retain::Nothing).await;
973f8fbdaf80atagen2026-09-28 09:10 UTC211
            Err(ApiError::unauthorized_stale(message, state.cookie_secure()))
973f8fbdaf80atagen2026-09-28 09:10 UTC212
        }
973f8fbdaf80atagen2026-09-28 09:10 UTC213
        Err(RestoreError::Unavailable(message)) => {
973f8fbdaf80atagen2026-09-28 09:10 UTC214
            Err(ApiError::new(StatusCode::BAD_GATEWAY, message))
973f8fbdaf80atagen2026-09-28 09:10 UTC215
        }
973f8fbdaf80atagen2026-09-28 09:10 UTC216
    }
973f8fbdaf80atagen2026-09-28 09:10 UTC217
}
973f8fbdaf80atagen2026-09-28 09:10 UTC218
973f8fbdaf80atagen2026-09-28 09:10 UTC219
/// commands answer when they're done, so the browser gets the outcome in the response
973f8fbdaf80atagen2026-09-28 09:10 UTC220
fn respond(result: Result<Reply, malice_core::error::CoreError>) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC221
    match result {
973f8fbdaf80atagen2026-09-28 09:10 UTC222
        Ok(Reply::Done) => no_content(None),
973f8fbdaf80atagen2026-09-28 09:10 UTC223
        Ok(Reply::Json(value)) => json_ok(&value, None),
973f8fbdaf80atagen2026-09-28 09:10 UTC224
        Ok(Reply::Avatar {
973f8fbdaf80atagen2026-09-28 09:10 UTC225
            bytes,
973f8fbdaf80atagen2026-09-28 09:10 UTC226
            content_type,
973f8fbdaf80atagen2026-09-28 09:10 UTC227
        }) => (
973f8fbdaf80atagen2026-09-28 09:10 UTC228
            [
973f8fbdaf80atagen2026-09-28 09:10 UTC229
                (axum::http::header::CONTENT_TYPE, content_type),
cf23dbf4e77catagen2026-10-23 06:36 UTC230
                // mxc uris name immutable content, a changed avatar gets a new uri
cf23dbf4e77catagen2026-10-23 06:36 UTC231
                (
cf23dbf4e77catagen2026-10-23 06:36 UTC232
                    axum::http::header::CACHE_CONTROL,
cf23dbf4e77catagen2026-10-23 06:36 UTC233
                    "private, max-age=31536000, immutable",
cf23dbf4e77catagen2026-10-23 06:36 UTC234
                ),
973f8fbdaf80atagen2026-09-28 09:10 UTC235
                (axum::http::header::X_CONTENT_TYPE_OPTIONS, "nosniff"),
973f8fbdaf80atagen2026-09-28 09:10 UTC236
            ],
973f8fbdaf80atagen2026-09-28 09:10 UTC237
            bytes,
973f8fbdaf80atagen2026-09-28 09:10 UTC238
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC239
            .into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC240
        Ok(Reply::Attachment {
973f8fbdaf80atagen2026-09-28 09:10 UTC241
            bytes,
973f8fbdaf80atagen2026-09-28 09:10 UTC242
            content_type,
973f8fbdaf80atagen2026-09-28 09:10 UTC243
        }) => (
973f8fbdaf80atagen2026-09-28 09:10 UTC244
            [
973f8fbdaf80atagen2026-09-28 09:10 UTC245
                (
973f8fbdaf80atagen2026-09-28 09:10 UTC246
                    axum::http::header::CONTENT_TYPE,
973f8fbdaf80atagen2026-09-28 09:10 UTC247
                    content_type.unwrap_or("application/octet-stream"),
973f8fbdaf80atagen2026-09-28 09:10 UTC248
                ),
973f8fbdaf80atagen2026-09-28 09:10 UTC249
                (
973f8fbdaf80atagen2026-09-28 09:10 UTC250
                    axum::http::header::CONTENT_DISPOSITION,
973f8fbdaf80atagen2026-09-28 09:10 UTC251
                    if content_type.is_some() {
973f8fbdaf80atagen2026-09-28 09:10 UTC252
                        "inline"
973f8fbdaf80atagen2026-09-28 09:10 UTC253
                    } else {
973f8fbdaf80atagen2026-09-28 09:10 UTC254
                        "attachment"
973f8fbdaf80atagen2026-09-28 09:10 UTC255
                    },
973f8fbdaf80atagen2026-09-28 09:10 UTC256
                ),
973f8fbdaf80atagen2026-09-28 09:10 UTC257
                (axum::http::header::CACHE_CONTROL, "private, max-age=3600"),
973f8fbdaf80atagen2026-09-28 09:10 UTC258
                (axum::http::header::X_CONTENT_TYPE_OPTIONS, "nosniff"),
973f8fbdaf80atagen2026-09-28 09:10 UTC259
                (axum::http::header::CONTENT_SECURITY_POLICY, "sandbox"),
973f8fbdaf80atagen2026-09-28 09:10 UTC260
            ],
973f8fbdaf80atagen2026-09-28 09:10 UTC261
            bytes,
973f8fbdaf80atagen2026-09-28 09:10 UTC262
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC263
            .into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC264
        Err(error) => ApiError::from(error).into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC265
    }
973f8fbdaf80atagen2026-09-28 09:10 UTC266
}
973f8fbdaf80atagen2026-09-28 09:10 UTC267
973f8fbdaf80atagen2026-09-28 09:10 UTC268
async fn get_session(State(state): State<AppState>, headers: HeaderMap) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC269
    let Some(sid) = session_sid(&headers) else {
973f8fbdaf80atagen2026-09-28 09:10 UTC270
        return json_ok(
973f8fbdaf80atagen2026-09-28 09:10 UTC271
            &SessionResponse {
973f8fbdaf80atagen2026-09-28 09:10 UTC272
                user_id: None,
973f8fbdaf80atagen2026-09-28 09:10 UTC273
                soft_logout: None,
973f8fbdaf80atagen2026-09-28 09:10 UTC274
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC275
            None,
973f8fbdaf80atagen2026-09-28 09:10 UTC276
        );
973f8fbdaf80atagen2026-09-28 09:10 UTC277
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC278
    match state.get_or_restore(&sid).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC279
        Ok(user) => json_ok(
973f8fbdaf80atagen2026-09-28 09:10 UTC280
            &SessionResponse {
973f8fbdaf80atagen2026-09-28 09:10 UTC281
                user_id: Some(user.user_id.clone()),
973f8fbdaf80atagen2026-09-28 09:10 UTC282
                soft_logout: None,
973f8fbdaf80atagen2026-09-28 09:10 UTC283
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC284
            None,
973f8fbdaf80atagen2026-09-28 09:10 UTC285
        ),
973f8fbdaf80atagen2026-09-28 09:10 UTC286
        Err(RestoreError::NotFound) => json_ok(
973f8fbdaf80atagen2026-09-28 09:10 UTC287
            &SessionResponse {
973f8fbdaf80atagen2026-09-28 09:10 UTC288
                user_id: None,
973f8fbdaf80atagen2026-09-28 09:10 UTC289
                soft_logout: None,
973f8fbdaf80atagen2026-09-28 09:10 UTC290
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC291
            None,
973f8fbdaf80atagen2026-09-28 09:10 UTC292
        ),
973f8fbdaf80atagen2026-09-28 09:10 UTC293
        Err(RestoreError::SoftLoggedOut {
973f8fbdaf80atagen2026-09-28 09:10 UTC294
            user_id,
973f8fbdaf80atagen2026-09-28 09:10 UTC295
            homeserver,
973f8fbdaf80atagen2026-09-28 09:10 UTC296
        }) => json_ok(
973f8fbdaf80atagen2026-09-28 09:10 UTC297
            &SessionResponse {
973f8fbdaf80atagen2026-09-28 09:10 UTC298
                user_id: None,
973f8fbdaf80atagen2026-09-28 09:10 UTC299
                soft_logout: Some(SoftLogoutInfo {
973f8fbdaf80atagen2026-09-28 09:10 UTC300
                    user_id,
973f8fbdaf80atagen2026-09-28 09:10 UTC301
                    homeserver,
973f8fbdaf80atagen2026-09-28 09:10 UTC302
                }),
973f8fbdaf80atagen2026-09-28 09:10 UTC303
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC304
            None,
973f8fbdaf80atagen2026-09-28 09:10 UTC305
        ),
973f8fbdaf80atagen2026-09-28 09:10 UTC306
        Err(RestoreError::Stale(message)) => {
973f8fbdaf80atagen2026-09-28 09:10 UTC307
            state.remove_session(&sid, Retain::Nothing).await;
973f8fbdaf80atagen2026-09-28 09:10 UTC308
            ApiError::unauthorized_stale(message, state.cookie_secure()).into_response()
973f8fbdaf80atagen2026-09-28 09:10 UTC309
        }
973f8fbdaf80atagen2026-09-28 09:10 UTC310
        Err(RestoreError::Unavailable(message)) => {
973f8fbdaf80atagen2026-09-28 09:10 UTC311
            ApiError::new(StatusCode::BAD_GATEWAY, message).into_response()
973f8fbdaf80atagen2026-09-28 09:10 UTC312
        }
973f8fbdaf80atagen2026-09-28 09:10 UTC313
    }
973f8fbdaf80atagen2026-09-28 09:10 UTC314
}
973f8fbdaf80atagen2026-09-28 09:10 UTC315
973f8fbdaf80atagen2026-09-28 09:10 UTC316
async fn post_login(
973f8fbdaf80atagen2026-09-28 09:10 UTC317
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC318
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC319
    body: axum::Json<LoginInput>,
973f8fbdaf80atagen2026-09-28 09:10 UTC320
) -> Response {
544a170ead2catagen2026-10-20 11:47 UTC321
    let username = body.0.username.trim().to_owned();
544a170ead2catagen2026-10-20 11:47 UTC322
    let password = body.0.password.clone();
544a170ead2catagen2026-10-20 11:47 UTC323
    let login = match body.0.into_login(state.allow_http_homeserver()) {
544a170ead2catagen2026-10-20 11:47 UTC324
        Ok(login) => login,
973f8fbdaf80atagen2026-09-28 09:10 UTC325
        Err(error) => return ApiError::from(error).into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC326
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC327
    let cookie_sid = session_sid(&headers);
973f8fbdaf80atagen2026-09-28 09:10 UTC328
    match state
544a170ead2catagen2026-10-20 11:47 UTC329
        .login(cookie_sid.as_deref(), login, &username, password)
973f8fbdaf80atagen2026-09-28 09:10 UTC330
        .await
973f8fbdaf80atagen2026-09-28 09:10 UTC331
    {
973f8fbdaf80atagen2026-09-28 09:10 UTC332
        Ok(user) => json_ok(
973f8fbdaf80atagen2026-09-28 09:10 UTC333
            &serde_json::json!({ "user_id": user.user_id }),
973f8fbdaf80atagen2026-09-28 09:10 UTC334
            Some(set_cookie_value(&user.sid, state.cookie_secure())),
973f8fbdaf80atagen2026-09-28 09:10 UTC335
        ),
544a170ead2catagen2026-10-20 11:47 UTC336
        Err(error) => ApiError::from(error).into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC337
    }
973f8fbdaf80atagen2026-09-28 09:10 UTC338
}
973f8fbdaf80atagen2026-09-28 09:10 UTC339
973f8fbdaf80atagen2026-09-28 09:10 UTC340
async fn post_logout(State(state): State<AppState>, headers: HeaderMap) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC341
    if let Some(sid) = session_sid(&headers) {
973f8fbdaf80atagen2026-09-28 09:10 UTC342
        state.logout(&sid).await;
973f8fbdaf80atagen2026-09-28 09:10 UTC343
    }
cf23dbf4e77catagen2026-10-23 06:36 UTC344
    let mut response = no_content(Some(crate::csrf::clear_cookie_value(state.cookie_secure())));
cf23dbf4e77catagen2026-10-23 06:36 UTC345
    response.headers_mut().insert(
cf23dbf4e77catagen2026-10-23 06:36 UTC346
        axum::http::header::HeaderName::from_static("clear-site-data"),
cf23dbf4e77catagen2026-10-23 06:36 UTC347
        axum::http::HeaderValue::from_static("\"cache\""),
cf23dbf4e77catagen2026-10-23 06:36 UTC348
    );
cf23dbf4e77catagen2026-10-23 06:36 UTC349
    response
973f8fbdaf80atagen2026-09-28 09:10 UTC350
}
973f8fbdaf80atagen2026-09-28 09:10 UTC351
973f8fbdaf80atagen2026-09-28 09:10 UTC352
async fn get_rooms(State(state): State<AppState>, headers: HeaderMap) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC353
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC354
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC355
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC356
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC357
    respond(dispatch(&user.handle, &user.mirror, Request::Rooms).await)
973f8fbdaf80atagen2026-09-28 09:10 UTC358
}
973f8fbdaf80atagen2026-09-28 09:10 UTC359
973f8fbdaf80atagen2026-09-28 09:10 UTC360
async fn get_members(
973f8fbdaf80atagen2026-09-28 09:10 UTC361
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC362
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC363
    Query(query): Query<MembersQuery>,
973f8fbdaf80atagen2026-09-28 09:10 UTC364
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC365
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC366
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC367
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC368
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC369
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC370
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC371
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC372
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC373
            Request::Members {
973f8fbdaf80atagen2026-09-28 09:10 UTC374
                room_id: query.room_id,
973f8fbdaf80atagen2026-09-28 09:10 UTC375
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC376
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC377
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC378
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC379
}
973f8fbdaf80atagen2026-09-28 09:10 UTC380
973f8fbdaf80atagen2026-09-28 09:10 UTC381
async fn rooms_open(
973f8fbdaf80atagen2026-09-28 09:10 UTC382
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC383
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC384
    body: axum::Json<RoomIdBody>,
973f8fbdaf80atagen2026-09-28 09:10 UTC385
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC386
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC387
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC388
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC389
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC390
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC391
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC392
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC393
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC394
            Request::Open {
973f8fbdaf80atagen2026-09-28 09:10 UTC395
                room_id: body.0.room_id,
973f8fbdaf80atagen2026-09-28 09:10 UTC396
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC397
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC398
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC399
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC400
}
973f8fbdaf80atagen2026-09-28 09:10 UTC401
973f8fbdaf80atagen2026-09-28 09:10 UTC402
async fn rooms_close(
973f8fbdaf80atagen2026-09-28 09:10 UTC403
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC404
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC405
    body: axum::Json<RoomIdBody>,
973f8fbdaf80atagen2026-09-28 09:10 UTC406
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC407
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC408
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC409
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC410
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC411
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC412
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC413
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC414
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC415
            Request::Close {
973f8fbdaf80atagen2026-09-28 09:10 UTC416
                room_id: body.0.room_id,
973f8fbdaf80atagen2026-09-28 09:10 UTC417
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC418
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC419
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC420
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC421
}
973f8fbdaf80atagen2026-09-28 09:10 UTC422
973f8fbdaf80atagen2026-09-28 09:10 UTC423
async fn rooms_send(
973f8fbdaf80atagen2026-09-28 09:10 UTC424
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC425
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC426
    body: axum::Json<SendBody>,
973f8fbdaf80atagen2026-09-28 09:10 UTC427
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC428
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC429
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC430
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC431
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC432
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC433
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC434
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC435
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC436
            Request::Send {
973f8fbdaf80atagen2026-09-28 09:10 UTC437
                room_id: body.0.room_id,
973f8fbdaf80atagen2026-09-28 09:10 UTC438
                body: body.0.body,
df3c0f2b3da4atagen2026-10-22 13:07 UTC439
                formatted_body: body.0.formatted_body,
df3c0f2b3da4atagen2026-10-22 13:07 UTC440
                reply_to: body.0.reply_to,
cf23dbf4e77catagen2026-10-23 06:36 UTC441
                mentions: body.0.mentions,
7cca1d5628d1atagen2026-10-24 07:36 UTC442
                kind: body.0.kind,
973f8fbdaf80atagen2026-09-28 09:10 UTC443
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC444
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC445
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC446
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC447
}
973f8fbdaf80atagen2026-09-28 09:10 UTC448
973f8fbdaf80atagen2026-09-28 09:10 UTC449
async fn rooms_upload(
973f8fbdaf80atagen2026-09-28 09:10 UTC450
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC451
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC452
    mut upload: Multipart,
973f8fbdaf80atagen2026-09-28 09:10 UTC453
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC454
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC455
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC456
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC457
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC458
    let mut room_id = None;
cf23dbf4e77catagen2026-10-23 06:36 UTC459
    let mut caption = None;
cf23dbf4e77catagen2026-10-23 06:36 UTC460
    let mut formatted_caption = None;
cf23dbf4e77catagen2026-10-23 06:36 UTC461
    let mut reply_to = None;
cf23dbf4e77catagen2026-10-23 06:36 UTC462
    let mut mentions = Vec::new();
973f8fbdaf80atagen2026-09-28 09:10 UTC463
    let mut file = None;
973f8fbdaf80atagen2026-09-28 09:10 UTC464
    loop {
973f8fbdaf80atagen2026-09-28 09:10 UTC465
        let field = match upload.next_field().await {
973f8fbdaf80atagen2026-09-28 09:10 UTC466
            Ok(Some(field)) => field,
973f8fbdaf80atagen2026-09-28 09:10 UTC467
            Ok(None) => break,
973f8fbdaf80atagen2026-09-28 09:10 UTC468
            Err(_) => return ApiError::bad_request("invalid upload form").into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC469
        };
973f8fbdaf80atagen2026-09-28 09:10 UTC470
        match field.name() {
973f8fbdaf80atagen2026-09-28 09:10 UTC471
            Some("room_id") if room_id.is_none() => {
973f8fbdaf80atagen2026-09-28 09:10 UTC472
                room_id = match field.text().await {
973f8fbdaf80atagen2026-09-28 09:10 UTC473
                    Ok(value) => Some(value),
973f8fbdaf80atagen2026-09-28 09:10 UTC474
                    Err(_) => return ApiError::bad_request("invalid room_id").into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC475
                };
973f8fbdaf80atagen2026-09-28 09:10 UTC476
            }
cf23dbf4e77catagen2026-10-23 06:36 UTC477
            Some("caption") if caption.is_none() => {
cf23dbf4e77catagen2026-10-23 06:36 UTC478
                caption = match field.text().await {
cf23dbf4e77catagen2026-10-23 06:36 UTC479
                    Ok(value) => Some(value),
cf23dbf4e77catagen2026-10-23 06:36 UTC480
                    Err(_) => return ApiError::bad_request("invalid caption").into_response(),
cf23dbf4e77catagen2026-10-23 06:36 UTC481
                };
cf23dbf4e77catagen2026-10-23 06:36 UTC482
            }
cf23dbf4e77catagen2026-10-23 06:36 UTC483
            Some("formatted_caption") if formatted_caption.is_none() => {
cf23dbf4e77catagen2026-10-23 06:36 UTC484
                formatted_caption = match field.text().await {
cf23dbf4e77catagen2026-10-23 06:36 UTC485
                    Ok(value) => Some(value),
cf23dbf4e77catagen2026-10-23 06:36 UTC486
                    Err(_) => return ApiError::bad_request("invalid caption").into_response(),
cf23dbf4e77catagen2026-10-23 06:36 UTC487
                };
cf23dbf4e77catagen2026-10-23 06:36 UTC488
            }
cf23dbf4e77catagen2026-10-23 06:36 UTC489
            Some("reply_to") if reply_to.is_none() => {
cf23dbf4e77catagen2026-10-23 06:36 UTC490
                reply_to = match field.text().await {
cf23dbf4e77catagen2026-10-23 06:36 UTC491
                    Ok(value) => Some(value),
cf23dbf4e77catagen2026-10-23 06:36 UTC492
                    Err(_) => return ApiError::bad_request("invalid reply_to").into_response(),
cf23dbf4e77catagen2026-10-23 06:36 UTC493
                };
cf23dbf4e77catagen2026-10-23 06:36 UTC494
            }
cf23dbf4e77catagen2026-10-23 06:36 UTC495
            Some("mentions") => match field.text().await {
cf23dbf4e77catagen2026-10-23 06:36 UTC496
                Ok(value) => mentions.push(value),
cf23dbf4e77catagen2026-10-23 06:36 UTC497
                Err(_) => return ApiError::bad_request("invalid mention").into_response(),
cf23dbf4e77catagen2026-10-23 06:36 UTC498
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC499
            Some("file") if file.is_none() => {
973f8fbdaf80atagen2026-09-28 09:10 UTC500
                let filename = field.file_name().unwrap_or("attachment").to_owned();
973f8fbdaf80atagen2026-09-28 09:10 UTC501
                let mimetype = field
973f8fbdaf80atagen2026-09-28 09:10 UTC502
                    .content_type()
973f8fbdaf80atagen2026-09-28 09:10 UTC503
                    .unwrap_or("application/octet-stream")
973f8fbdaf80atagen2026-09-28 09:10 UTC504
                    .to_owned();
973f8fbdaf80atagen2026-09-28 09:10 UTC505
                let bytes = match field.bytes().await {
973f8fbdaf80atagen2026-09-28 09:10 UTC506
                    Ok(bytes) => bytes,
973f8fbdaf80atagen2026-09-28 09:10 UTC507
                    Err(_) => return ApiError::bad_request("invalid file").into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC508
                };
973f8fbdaf80atagen2026-09-28 09:10 UTC509
                if bytes.is_empty() || bytes.len() > 20 * 1024 * 1024 {
973f8fbdaf80atagen2026-09-28 09:10 UTC510
                    return ApiError::bad_request("file must be between 1 byte and 20 MiB")
973f8fbdaf80atagen2026-09-28 09:10 UTC511
                        .into_response();
973f8fbdaf80atagen2026-09-28 09:10 UTC512
                }
973f8fbdaf80atagen2026-09-28 09:10 UTC513
                file = Some((filename, mimetype, bytes));
973f8fbdaf80atagen2026-09-28 09:10 UTC514
            }
973f8fbdaf80atagen2026-09-28 09:10 UTC515
            _ => return ApiError::bad_request("unexpected upload field").into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC516
        }
973f8fbdaf80atagen2026-09-28 09:10 UTC517
    }
973f8fbdaf80atagen2026-09-28 09:10 UTC518
    let Some(room_id) = room_id else {
973f8fbdaf80atagen2026-09-28 09:10 UTC519
        return ApiError::bad_request("room_id is required").into_response();
973f8fbdaf80atagen2026-09-28 09:10 UTC520
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC521
    let Some((filename, mimetype, bytes)) = file else {
973f8fbdaf80atagen2026-09-28 09:10 UTC522
        return ApiError::bad_request("file is required").into_response();
973f8fbdaf80atagen2026-09-28 09:10 UTC523
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC524
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC525
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC526
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC527
            &user.mirror,
cf23dbf4e77catagen2026-10-23 06:36 UTC528
            Request::Upload {
cf23dbf4e77catagen2026-10-23 06:36 UTC529
                room_id,
cf23dbf4e77catagen2026-10-23 06:36 UTC530
                filename,
cf23dbf4e77catagen2026-10-23 06:36 UTC531
                mimetype,
cf23dbf4e77catagen2026-10-23 06:36 UTC532
                caption,
cf23dbf4e77catagen2026-10-23 06:36 UTC533
                formatted_caption,
cf23dbf4e77catagen2026-10-23 06:36 UTC534
                reply_to,
cf23dbf4e77catagen2026-10-23 06:36 UTC535
                mentions,
cf23dbf4e77catagen2026-10-23 06:36 UTC536
                bytes: bytes.to_vec(),
cf23dbf4e77catagen2026-10-23 06:36 UTC537
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC538
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC539
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC540
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC541
}
973f8fbdaf80atagen2026-09-28 09:10 UTC542
e4c64c36f105atagen2026-10-24 07:09 UTC543
/// Ops added after the per-route handlers, taken as the core request JSON. Anything
d8624c214c04atagen2026-10-24 08:24 UTC544
/// carrying bytes stays on its own route.
e4c64c36f105atagen2026-10-24 07:09 UTC545
async fn post_command(
e4c64c36f105atagen2026-10-24 07:09 UTC546
    State(state): State<AppState>,
e4c64c36f105atagen2026-10-24 07:09 UTC547
    headers: HeaderMap,
e4c64c36f105atagen2026-10-24 07:09 UTC548
    body: axum::Json<Request>,
e4c64c36f105atagen2026-10-24 07:09 UTC549
) -> Response {
e4c64c36f105atagen2026-10-24 07:09 UTC550
    let (user, _) = match auth(&state, &headers).await {
e4c64c36f105atagen2026-10-24 07:09 UTC551
        Ok(pair) => pair,
e4c64c36f105atagen2026-10-24 07:09 UTC552
        Err(error) => return error.into_response(),
e4c64c36f105atagen2026-10-24 07:09 UTC553
    };
e4c64c36f105atagen2026-10-24 07:09 UTC554
    let request = body.0;
e4c64c36f105atagen2026-10-24 07:09 UTC555
    if !matches!(
e4c64c36f105atagen2026-10-24 07:09 UTC556
        request,
e4c64c36f105atagen2026-10-24 07:09 UTC557
        Request::Kick { .. }
e4c64c36f105atagen2026-10-24 07:09 UTC558
            | Request::Ban { .. }
e4c64c36f105atagen2026-10-24 07:09 UTC559
            | Request::Unban { .. }
e4c64c36f105atagen2026-10-24 07:09 UTC560
            | Request::SetPowerLevel { .. }
e4c64c36f105atagen2026-10-24 07:09 UTC561
            | Request::SetDisplayName { .. }
e4c64c36f105atagen2026-10-24 07:09 UTC562
            | Request::SetAvatar { .. }
e4c64c36f105atagen2026-10-24 07:09 UTC563
            | Request::Invite { .. }
e4c64c36f105atagen2026-10-24 07:09 UTC564
            | Request::Direct { .. }
e4c64c36f105atagen2026-10-24 07:09 UTC565
            | Request::Redact { .. }
7cca1d5628d1atagen2026-10-24 07:36 UTC566
            | Request::SetFavourite { .. }
7cca1d5628d1atagen2026-10-24 07:36 UTC567
            | Request::ReorderSpaces { .. }
7cca1d5628d1atagen2026-10-24 07:36 UTC568
            | Request::SetRoomDisplayName { .. }
7cca1d5628d1atagen2026-10-24 07:36 UTC569
            | Request::SetRoomAvatar { .. }
7cca1d5628d1atagen2026-10-24 07:36 UTC570
            | Request::Mute { .. }
7cca1d5628d1atagen2026-10-24 07:36 UTC571
            | Request::Ignore { .. }
7cca1d5628d1atagen2026-10-24 07:36 UTC572
            | Request::Unignore { .. }
d8624c214c04atagen2026-10-24 08:24 UTC573
            | Request::PowerLevels { .. }
77eddb6b710fatagen2026-10-24 09:32 UTC574
            | Request::OpenPinned { .. }
77eddb6b710fatagen2026-10-24 09:32 UTC575
            | Request::ClosePinned { .. }
77eddb6b710fatagen2026-10-24 09:32 UTC576
            | Request::SetPinned { .. }
d095131eec0eatagen2026-10-24 08:39 UTC577
            | Request::SendSticker { .. }
d095131eec0eatagen2026-10-24 08:39 UTC578
            | Request::CreateRoomPack { .. }
d095131eec0eatagen2026-10-24 08:39 UTC579
            | Request::DeletePack { .. }
d095131eec0eatagen2026-10-24 08:39 UTC580
            | Request::SetPackMeta { .. }
d095131eec0eatagen2026-10-24 08:39 UTC581
            | Request::PutPackImage { .. }
d095131eec0eatagen2026-10-24 08:39 UTC582
            | Request::RenamePackImage { .. }
d095131eec0eatagen2026-10-24 08:39 UTC583
            | Request::RemovePackImage { .. }
d095131eec0eatagen2026-10-24 08:39 UTC584
            | Request::SetPackEnabled { .. }
d095131eec0eatagen2026-10-24 08:39 UTC585
            | Request::ClonePackImage { .. }
d095131eec0eatagen2026-10-24 08:39 UTC586
            | Request::CloneSticker { .. }
d095131eec0eatagen2026-10-24 08:39 UTC587
            | Request::ClonePack { .. }
e4c64c36f105atagen2026-10-24 07:09 UTC588
    ) {
e4c64c36f105atagen2026-10-24 07:09 UTC589
        return ApiError::bad_request("op is not available as a command").into_response();
e4c64c36f105atagen2026-10-24 07:09 UTC590
    }
e4c64c36f105atagen2026-10-24 07:09 UTC591
    respond(dispatch(&user.handle, &user.mirror, request).await)
e4c64c36f105atagen2026-10-24 07:09 UTC592
}
e4c64c36f105atagen2026-10-24 07:09 UTC593
e4c64c36f105atagen2026-10-24 07:09 UTC594
async fn profile_avatar(
e4c64c36f105atagen2026-10-24 07:09 UTC595
    State(state): State<AppState>,
e4c64c36f105atagen2026-10-24 07:09 UTC596
    headers: HeaderMap,
e4c64c36f105atagen2026-10-24 07:09 UTC597
    bytes: axum::body::Bytes,
e4c64c36f105atagen2026-10-24 07:09 UTC598
) -> Response {
e4c64c36f105atagen2026-10-24 07:09 UTC599
    let (user, _) = match auth(&state, &headers).await {
e4c64c36f105atagen2026-10-24 07:09 UTC600
        Ok(pair) => pair,
e4c64c36f105atagen2026-10-24 07:09 UTC601
        Err(error) => return error.into_response(),
e4c64c36f105atagen2026-10-24 07:09 UTC602
    };
e4c64c36f105atagen2026-10-24 07:09 UTC603
    respond(
e4c64c36f105atagen2026-10-24 07:09 UTC604
        dispatch(
e4c64c36f105atagen2026-10-24 07:09 UTC605
            &user.handle,
e4c64c36f105atagen2026-10-24 07:09 UTC606
            &user.mirror,
e4c64c36f105atagen2026-10-24 07:09 UTC607
            Request::UploadAvatar {
e4c64c36f105atagen2026-10-24 07:09 UTC608
                bytes: bytes.to_vec(),
e4c64c36f105atagen2026-10-24 07:09 UTC609
            },
e4c64c36f105atagen2026-10-24 07:09 UTC610
        )
e4c64c36f105atagen2026-10-24 07:09 UTC611
        .await,
e4c64c36f105atagen2026-10-24 07:09 UTC612
    )
e4c64c36f105atagen2026-10-24 07:09 UTC613
}
e4c64c36f105atagen2026-10-24 07:09 UTC614
7cca1d5628d1atagen2026-10-24 07:36 UTC615
#[derive(Debug, Deserialize)]
7cca1d5628d1atagen2026-10-24 07:36 UTC616
struct RoomAvatarQuery {
7cca1d5628d1atagen2026-10-24 07:36 UTC617
    room_id: String,
7cca1d5628d1atagen2026-10-24 07:36 UTC618
}
7cca1d5628d1atagen2026-10-24 07:36 UTC619
7cca1d5628d1atagen2026-10-24 07:36 UTC620
async fn rooms_avatar(
7cca1d5628d1atagen2026-10-24 07:36 UTC621
    State(state): State<AppState>,
7cca1d5628d1atagen2026-10-24 07:36 UTC622
    headers: HeaderMap,
7cca1d5628d1atagen2026-10-24 07:36 UTC623
    Query(query): Query<RoomAvatarQuery>,
7cca1d5628d1atagen2026-10-24 07:36 UTC624
    bytes: axum::body::Bytes,
7cca1d5628d1atagen2026-10-24 07:36 UTC625
) -> Response {
7cca1d5628d1atagen2026-10-24 07:36 UTC626
    let (user, _) = match auth(&state, &headers).await {
7cca1d5628d1atagen2026-10-24 07:36 UTC627
        Ok(pair) => pair,
7cca1d5628d1atagen2026-10-24 07:36 UTC628
        Err(error) => return error.into_response(),
7cca1d5628d1atagen2026-10-24 07:36 UTC629
    };
7cca1d5628d1atagen2026-10-24 07:36 UTC630
    respond(
7cca1d5628d1atagen2026-10-24 07:36 UTC631
        dispatch(
7cca1d5628d1atagen2026-10-24 07:36 UTC632
            &user.handle,
7cca1d5628d1atagen2026-10-24 07:36 UTC633
            &user.mirror,
7cca1d5628d1atagen2026-10-24 07:36 UTC634
            Request::UploadRoomAvatar {
7cca1d5628d1atagen2026-10-24 07:36 UTC635
                room_id: query.room_id,
7cca1d5628d1atagen2026-10-24 07:36 UTC636
                bytes: bytes.to_vec(),
7cca1d5628d1atagen2026-10-24 07:36 UTC637
            },
7cca1d5628d1atagen2026-10-24 07:36 UTC638
        )
7cca1d5628d1atagen2026-10-24 07:36 UTC639
        .await,
7cca1d5628d1atagen2026-10-24 07:36 UTC640
    )
7cca1d5628d1atagen2026-10-24 07:36 UTC641
}
7cca1d5628d1atagen2026-10-24 07:36 UTC642
d095131eec0eatagen2026-10-24 08:39 UTC643
async fn media_upload(
d095131eec0eatagen2026-10-24 08:39 UTC644
    State(state): State<AppState>,
d095131eec0eatagen2026-10-24 08:39 UTC645
    headers: HeaderMap,
d095131eec0eatagen2026-10-24 08:39 UTC646
    bytes: axum::body::Bytes,
d095131eec0eatagen2026-10-24 08:39 UTC647
) -> Response {
d095131eec0eatagen2026-10-24 08:39 UTC648
    let (user, _) = match auth(&state, &headers).await {
d095131eec0eatagen2026-10-24 08:39 UTC649
        Ok(pair) => pair,
d095131eec0eatagen2026-10-24 08:39 UTC650
        Err(error) => return error.into_response(),
d095131eec0eatagen2026-10-24 08:39 UTC651
    };
d095131eec0eatagen2026-10-24 08:39 UTC652
    respond(
d095131eec0eatagen2026-10-24 08:39 UTC653
        dispatch(
d095131eec0eatagen2026-10-24 08:39 UTC654
            &user.handle,
d095131eec0eatagen2026-10-24 08:39 UTC655
            &user.mirror,
d095131eec0eatagen2026-10-24 08:39 UTC656
            Request::UploadMedia {
d095131eec0eatagen2026-10-24 08:39 UTC657
                bytes: bytes.to_vec(),
d095131eec0eatagen2026-10-24 08:39 UTC658
            },
d095131eec0eatagen2026-10-24 08:39 UTC659
        )
d095131eec0eatagen2026-10-24 08:39 UTC660
        .await,
d095131eec0eatagen2026-10-24 08:39 UTC661
    )
d095131eec0eatagen2026-10-24 08:39 UTC662
}
d095131eec0eatagen2026-10-24 08:39 UTC663
973f8fbdaf80atagen2026-09-28 09:10 UTC664
async fn rooms_paginate(
973f8fbdaf80atagen2026-09-28 09:10 UTC665
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC666
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC667
    body: axum::Json<PaginateBody>,
973f8fbdaf80atagen2026-09-28 09:10 UTC668
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC669
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC670
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC671
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC672
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC673
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC674
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC675
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC676
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC677
            Request::Paginate {
973f8fbdaf80atagen2026-09-28 09:10 UTC678
                room_id: body.0.room_id,
973f8fbdaf80atagen2026-09-28 09:10 UTC679
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC680
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC681
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC682
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC683
}
973f8fbdaf80atagen2026-09-28 09:10 UTC684
824dfd38cdf3atagen2026-10-24 13:43 UTC685
async fn rooms_context(
824dfd38cdf3atagen2026-10-24 13:43 UTC686
    State(state): State<AppState>,
824dfd38cdf3atagen2026-10-24 13:43 UTC687
    headers: HeaderMap,
824dfd38cdf3atagen2026-10-24 13:43 UTC688
    body: axum::Json<EventContextBody>,
824dfd38cdf3atagen2026-10-24 13:43 UTC689
) -> Response {
824dfd38cdf3atagen2026-10-24 13:43 UTC690
    let (user, _) = match auth(&state, &headers).await {
824dfd38cdf3atagen2026-10-24 13:43 UTC691
        Ok(pair) => pair,
824dfd38cdf3atagen2026-10-24 13:43 UTC692
        Err(error) => return error.into_response(),
824dfd38cdf3atagen2026-10-24 13:43 UTC693
    };
824dfd38cdf3atagen2026-10-24 13:43 UTC694
    respond(
824dfd38cdf3atagen2026-10-24 13:43 UTC695
        dispatch(
824dfd38cdf3atagen2026-10-24 13:43 UTC696
            &user.handle,
824dfd38cdf3atagen2026-10-24 13:43 UTC697
            &user.mirror,
824dfd38cdf3atagen2026-10-24 13:43 UTC698
            Request::EventContext {
824dfd38cdf3atagen2026-10-24 13:43 UTC699
                room_id: body.0.room_id,
824dfd38cdf3atagen2026-10-24 13:43 UTC700
                event_id: body.0.event_id,
824dfd38cdf3atagen2026-10-24 13:43 UTC701
                before_limit: body.0.before_limit,
824dfd38cdf3atagen2026-10-24 13:43 UTC702
                after_limit: body.0.after_limit,
824dfd38cdf3atagen2026-10-24 13:43 UTC703
            },
824dfd38cdf3atagen2026-10-24 13:43 UTC704
        )
824dfd38cdf3atagen2026-10-24 13:43 UTC705
        .await,
824dfd38cdf3atagen2026-10-24 13:43 UTC706
    )
824dfd38cdf3atagen2026-10-24 13:43 UTC707
}
824dfd38cdf3atagen2026-10-24 13:43 UTC708
824dfd38cdf3atagen2026-10-24 13:43 UTC709
async fn rooms_context_before(
824dfd38cdf3atagen2026-10-24 13:43 UTC710
    State(state): State<AppState>,
824dfd38cdf3atagen2026-10-24 13:43 UTC711
    headers: HeaderMap,
824dfd38cdf3atagen2026-10-24 13:43 UTC712
    body: axum::Json<EventContextPageBody>,
824dfd38cdf3atagen2026-10-24 13:43 UTC713
) -> Response {
824dfd38cdf3atagen2026-10-24 13:43 UTC714
    let (user, _) = match auth(&state, &headers).await {
824dfd38cdf3atagen2026-10-24 13:43 UTC715
        Ok(pair) => pair,
824dfd38cdf3atagen2026-10-24 13:43 UTC716
        Err(error) => return error.into_response(),
824dfd38cdf3atagen2026-10-24 13:43 UTC717
    };
824dfd38cdf3atagen2026-10-24 13:43 UTC718
    respond(
824dfd38cdf3atagen2026-10-24 13:43 UTC719
        dispatch(
824dfd38cdf3atagen2026-10-24 13:43 UTC720
            &user.handle,
824dfd38cdf3atagen2026-10-24 13:43 UTC721
            &user.mirror,
824dfd38cdf3atagen2026-10-24 13:43 UTC722
            Request::EventContextBefore {
824dfd38cdf3atagen2026-10-24 13:43 UTC723
                room_id: body.0.room_id,
824dfd38cdf3atagen2026-10-24 13:43 UTC724
                token: body.0.token,
824dfd38cdf3atagen2026-10-24 13:43 UTC725
                limit: body.0.limit,
824dfd38cdf3atagen2026-10-24 13:43 UTC726
            },
824dfd38cdf3atagen2026-10-24 13:43 UTC727
        )
824dfd38cdf3atagen2026-10-24 13:43 UTC728
        .await,
824dfd38cdf3atagen2026-10-24 13:43 UTC729
    )
824dfd38cdf3atagen2026-10-24 13:43 UTC730
}
824dfd38cdf3atagen2026-10-24 13:43 UTC731
824dfd38cdf3atagen2026-10-24 13:43 UTC732
async fn rooms_context_after(
824dfd38cdf3atagen2026-10-24 13:43 UTC733
    State(state): State<AppState>,
824dfd38cdf3atagen2026-10-24 13:43 UTC734
    headers: HeaderMap,
824dfd38cdf3atagen2026-10-24 13:43 UTC735
    body: axum::Json<EventContextPageBody>,
824dfd38cdf3atagen2026-10-24 13:43 UTC736
) -> Response {
824dfd38cdf3atagen2026-10-24 13:43 UTC737
    let (user, _) = match auth(&state, &headers).await {
824dfd38cdf3atagen2026-10-24 13:43 UTC738
        Ok(pair) => pair,
824dfd38cdf3atagen2026-10-24 13:43 UTC739
        Err(error) => return error.into_response(),
824dfd38cdf3atagen2026-10-24 13:43 UTC740
    };
824dfd38cdf3atagen2026-10-24 13:43 UTC741
    respond(
824dfd38cdf3atagen2026-10-24 13:43 UTC742
        dispatch(
824dfd38cdf3atagen2026-10-24 13:43 UTC743
            &user.handle,
824dfd38cdf3atagen2026-10-24 13:43 UTC744
            &user.mirror,
824dfd38cdf3atagen2026-10-24 13:43 UTC745
            Request::EventContextAfter {
824dfd38cdf3atagen2026-10-24 13:43 UTC746
                room_id: body.0.room_id,
824dfd38cdf3atagen2026-10-24 13:43 UTC747
                token: body.0.token,
824dfd38cdf3atagen2026-10-24 13:43 UTC748
                limit: body.0.limit,
824dfd38cdf3atagen2026-10-24 13:43 UTC749
            },
824dfd38cdf3atagen2026-10-24 13:43 UTC750
        )
824dfd38cdf3atagen2026-10-24 13:43 UTC751
        .await,
824dfd38cdf3atagen2026-10-24 13:43 UTC752
    )
824dfd38cdf3atagen2026-10-24 13:43 UTC753
}
824dfd38cdf3atagen2026-10-24 13:43 UTC754
973f8fbdaf80atagen2026-09-28 09:10 UTC755
async fn rooms_join(
973f8fbdaf80atagen2026-09-28 09:10 UTC756
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC757
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC758
    body: axum::Json<JoinBody>,
973f8fbdaf80atagen2026-09-28 09:10 UTC759
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC760
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC761
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC762
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC763
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC764
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC765
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC766
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC767
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC768
            Request::Join { room: body.0.room },
973f8fbdaf80atagen2026-09-28 09:10 UTC769
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC770
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC771
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC772
}
973f8fbdaf80atagen2026-09-28 09:10 UTC773
973f8fbdaf80atagen2026-09-28 09:10 UTC774
async fn rooms_create(
973f8fbdaf80atagen2026-09-28 09:10 UTC775
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC776
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC777
    body: axum::Json<CreateBody>,
973f8fbdaf80atagen2026-09-28 09:10 UTC778
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC779
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC780
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC781
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC782
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC783
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC784
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC785
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC786
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC787
            Request::Create {
973f8fbdaf80atagen2026-09-28 09:10 UTC788
                name: body.0.name,
973f8fbdaf80atagen2026-09-28 09:10 UTC789
                topic: body.0.topic,
973f8fbdaf80atagen2026-09-28 09:10 UTC790
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC791
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC792
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC793
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC794
}
973f8fbdaf80atagen2026-09-28 09:10 UTC795
973f8fbdaf80atagen2026-09-28 09:10 UTC796
async fn rooms_leave(
973f8fbdaf80atagen2026-09-28 09:10 UTC797
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC798
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC799
    body: axum::Json<RoomIdBody>,
973f8fbdaf80atagen2026-09-28 09:10 UTC800
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC801
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC802
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC803
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC804
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC805
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC806
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC807
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC808
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC809
            Request::Leave {
973f8fbdaf80atagen2026-09-28 09:10 UTC810
                room_id: body.0.room_id,
973f8fbdaf80atagen2026-09-28 09:10 UTC811
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC812
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC813
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC814
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC815
}
973f8fbdaf80atagen2026-09-28 09:10 UTC816
cf23dbf4e77catagen2026-10-23 06:36 UTC817
async fn rooms_edit(
cf23dbf4e77catagen2026-10-23 06:36 UTC818
    State(state): State<AppState>,
cf23dbf4e77catagen2026-10-23 06:36 UTC819
    headers: HeaderMap,
cf23dbf4e77catagen2026-10-23 06:36 UTC820
    body: axum::Json<EditBody>,
cf23dbf4e77catagen2026-10-23 06:36 UTC821
) -> Response {
cf23dbf4e77catagen2026-10-23 06:36 UTC822
    let (user, _) = match auth(&state, &headers).await {
cf23dbf4e77catagen2026-10-23 06:36 UTC823
        Ok(pair) => pair,
cf23dbf4e77catagen2026-10-23 06:36 UTC824
        Err(error) => return error.into_response(),
cf23dbf4e77catagen2026-10-23 06:36 UTC825
    };
cf23dbf4e77catagen2026-10-23 06:36 UTC826
    respond(
cf23dbf4e77catagen2026-10-23 06:36 UTC827
        dispatch(
cf23dbf4e77catagen2026-10-23 06:36 UTC828
            &user.handle,
cf23dbf4e77catagen2026-10-23 06:36 UTC829
            &user.mirror,
cf23dbf4e77catagen2026-10-23 06:36 UTC830
            Request::Edit {
cf23dbf4e77catagen2026-10-23 06:36 UTC831
                room_id: body.0.room_id,
cf23dbf4e77catagen2026-10-23 06:36 UTC832
                event_id: body.0.event_id,
cf23dbf4e77catagen2026-10-23 06:36 UTC833
                body: body.0.body,
cf23dbf4e77catagen2026-10-23 06:36 UTC834
                formatted_body: body.0.formatted_body,
cf23dbf4e77catagen2026-10-23 06:36 UTC835
                mentions: body.0.mentions,
cf23dbf4e77catagen2026-10-23 06:36 UTC836
            },
cf23dbf4e77catagen2026-10-23 06:36 UTC837
        )
cf23dbf4e77catagen2026-10-23 06:36 UTC838
        .await,
cf23dbf4e77catagen2026-10-23 06:36 UTC839
    )
cf23dbf4e77catagen2026-10-23 06:36 UTC840
}
cf23dbf4e77catagen2026-10-23 06:36 UTC841
973f8fbdaf80atagen2026-09-28 09:10 UTC842
async fn rooms_react(
973f8fbdaf80atagen2026-09-28 09:10 UTC843
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC844
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC845
    body: axum::Json<ReactBody>,
973f8fbdaf80atagen2026-09-28 09:10 UTC846
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC847
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC848
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC849
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC850
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC851
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC852
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC853
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC854
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC855
            Request::React {
973f8fbdaf80atagen2026-09-28 09:10 UTC856
                room_id: body.0.room_id,
973f8fbdaf80atagen2026-09-28 09:10 UTC857
                event_id: body.0.event_id,
973f8fbdaf80atagen2026-09-28 09:10 UTC858
                key: body.0.key,
ecb8598b38afatagen2026-10-22 09:19 UTC859
                shortcode: body.0.shortcode,
973f8fbdaf80atagen2026-09-28 09:10 UTC860
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC861
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC862
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC863
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC864
}
973f8fbdaf80atagen2026-09-28 09:10 UTC865
973f8fbdaf80atagen2026-09-28 09:10 UTC866
async fn rooms_read(
973f8fbdaf80atagen2026-09-28 09:10 UTC867
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC868
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC869
    body: axum::Json<ReadBody>,
973f8fbdaf80atagen2026-09-28 09:10 UTC870
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC871
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC872
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC873
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC874
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC875
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC876
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC877
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC878
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC879
            Request::Read {
973f8fbdaf80atagen2026-09-28 09:10 UTC880
                room_id: body.0.room_id,
973f8fbdaf80atagen2026-09-28 09:10 UTC881
                event_id: body.0.event_id,
973f8fbdaf80atagen2026-09-28 09:10 UTC882
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC883
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC884
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC885
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC886
}
973f8fbdaf80atagen2026-09-28 09:10 UTC887
973f8fbdaf80atagen2026-09-28 09:10 UTC888
async fn get_profile(State(state): State<AppState>, headers: HeaderMap) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC889
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC890
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC891
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC892
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC893
    respond(dispatch(&user.handle, &user.mirror, Request::Profile).await)
973f8fbdaf80atagen2026-09-28 09:10 UTC894
}
973f8fbdaf80atagen2026-09-28 09:10 UTC895
973f8fbdaf80atagen2026-09-28 09:10 UTC896
async fn get_space_children(
973f8fbdaf80atagen2026-09-28 09:10 UTC897
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC898
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC899
    Query(query): Query<MembersQuery>,
973f8fbdaf80atagen2026-09-28 09:10 UTC900
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC901
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC902
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC903
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC904
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC905
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC906
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC907
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC908
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC909
            Request::SpaceChildren {
973f8fbdaf80atagen2026-09-28 09:10 UTC910
                room_id: query.room_id,
973f8fbdaf80atagen2026-09-28 09:10 UTC911
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC912
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC913
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC914
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC915
}
973f8fbdaf80atagen2026-09-28 09:10 UTC916
973f8fbdaf80atagen2026-09-28 09:10 UTC917
async fn get_media(
973f8fbdaf80atagen2026-09-28 09:10 UTC918
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC919
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC920
    Query(query): Query<MediaQuery>,
973f8fbdaf80atagen2026-09-28 09:10 UTC921
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC922
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC923
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC924
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC925
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC926
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC927
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC928
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC929
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC930
            Request::Avatar { mxc: query.mxc },
973f8fbdaf80atagen2026-09-28 09:10 UTC931
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC932
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC933
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC934
}
973f8fbdaf80atagen2026-09-28 09:10 UTC935
973f8fbdaf80atagen2026-09-28 09:10 UTC936
async fn get_message_media(
973f8fbdaf80atagen2026-09-28 09:10 UTC937
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC938
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC939
    Query(query): Query<MessageMediaQuery>,
973f8fbdaf80atagen2026-09-28 09:10 UTC940
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC941
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC942
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC943
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC944
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC945
    // the declared mimetype is the sender's claim, so the bytes get sniffed instead
973f8fbdaf80atagen2026-09-28 09:10 UTC946
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC947
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC948
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC949
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC950
            Request::Media {
973f8fbdaf80atagen2026-09-28 09:10 UTC951
                media_id: query.media_id,
973f8fbdaf80atagen2026-09-28 09:10 UTC952
                size: query.size,
973f8fbdaf80atagen2026-09-28 09:10 UTC953
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC954
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC955
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC956
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC957
}
973f8fbdaf80atagen2026-09-28 09:10 UTC958
973f8fbdaf80atagen2026-09-28 09:10 UTC959
async fn get_encryption(State(state): State<AppState>, headers: HeaderMap) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC960
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC961
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC962
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC963
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC964
    respond(dispatch(&user.handle, &user.mirror, Request::Encryption).await)
973f8fbdaf80atagen2026-09-28 09:10 UTC965
}
973f8fbdaf80atagen2026-09-28 09:10 UTC966
973f8fbdaf80atagen2026-09-28 09:10 UTC967
async fn recover_encryption(
973f8fbdaf80atagen2026-09-28 09:10 UTC968
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC969
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC970
    body: axum::Json<RecoverBody>,
973f8fbdaf80atagen2026-09-28 09:10 UTC971
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC972
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC973
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC974
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC975
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC976
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC977
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC978
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC979
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC980
            Request::Recover {
973f8fbdaf80atagen2026-09-28 09:10 UTC981
                recovery_key: body.0.recovery_key,
973f8fbdaf80atagen2026-09-28 09:10 UTC982
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC983
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC984
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC985
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC986
}
973f8fbdaf80atagen2026-09-28 09:10 UTC987
973f8fbdaf80atagen2026-09-28 09:10 UTC988
async fn start_verification(State(state): State<AppState>, headers: HeaderMap) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC989
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC990
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC991
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC992
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC993
    respond(dispatch(&user.handle, &user.mirror, Request::StartVerification).await)
973f8fbdaf80atagen2026-09-28 09:10 UTC994
}
973f8fbdaf80atagen2026-09-28 09:10 UTC995
973f8fbdaf80atagen2026-09-28 09:10 UTC996
async fn verify_encryption(
973f8fbdaf80atagen2026-09-28 09:10 UTC997
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC998
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC999
    body: axum::Json<VerifyBody>,
973f8fbdaf80atagen2026-09-28 09:10 UTC1000
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC1001
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC1002
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC1003
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC1004
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC1005
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC1006
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC1007
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC1008
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC1009
            Request::Verify {
973f8fbdaf80atagen2026-09-28 09:10 UTC1010
                action: body.0.action,
973f8fbdaf80atagen2026-09-28 09:10 UTC1011
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC1012
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC1013
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC1014
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC1015
}
973f8fbdaf80atagen2026-09-28 09:10 UTC1016
973f8fbdaf80atagen2026-09-28 09:10 UTC1017
async fn rooms_typing(
973f8fbdaf80atagen2026-09-28 09:10 UTC1018
    State(state): State<AppState>,
973f8fbdaf80atagen2026-09-28 09:10 UTC1019
    headers: HeaderMap,
973f8fbdaf80atagen2026-09-28 09:10 UTC1020
    body: axum::Json<TypingBody>,
973f8fbdaf80atagen2026-09-28 09:10 UTC1021
) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC1022
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC1023
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC1024
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC1025
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC1026
    respond(
973f8fbdaf80atagen2026-09-28 09:10 UTC1027
        dispatch(
973f8fbdaf80atagen2026-09-28 09:10 UTC1028
            &user.handle,
973f8fbdaf80atagen2026-09-28 09:10 UTC1029
            &user.mirror,
973f8fbdaf80atagen2026-09-28 09:10 UTC1030
            Request::Typing {
973f8fbdaf80atagen2026-09-28 09:10 UTC1031
                room_id: body.0.room_id,
973f8fbdaf80atagen2026-09-28 09:10 UTC1032
                typing: body.0.typing,
973f8fbdaf80atagen2026-09-28 09:10 UTC1033
            },
973f8fbdaf80atagen2026-09-28 09:10 UTC1034
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC1035
        .await,
973f8fbdaf80atagen2026-09-28 09:10 UTC1036
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC1037
}
973f8fbdaf80atagen2026-09-28 09:10 UTC1038
544a170ead2catagen2026-10-20 11:47 UTC1039
async fn get_account_data(
544a170ead2catagen2026-10-20 11:47 UTC1040
    State(state): State<AppState>,
544a170ead2catagen2026-10-20 11:47 UTC1041
    headers: HeaderMap,
544a170ead2catagen2026-10-20 11:47 UTC1042
    Query(query): Query<AccountDataQuery>,
544a170ead2catagen2026-10-20 11:47 UTC1043
) -> Response {
544a170ead2catagen2026-10-20 11:47 UTC1044
    let (user, _) = match auth(&state, &headers).await {
544a170ead2catagen2026-10-20 11:47 UTC1045
        Ok(pair) => pair,
544a170ead2catagen2026-10-20 11:47 UTC1046
        Err(error) => return error.into_response(),
544a170ead2catagen2026-10-20 11:47 UTC1047
    };
544a170ead2catagen2026-10-20 11:47 UTC1048
    respond(
544a170ead2catagen2026-10-20 11:47 UTC1049
        dispatch(
544a170ead2catagen2026-10-20 11:47 UTC1050
            &user.handle,
544a170ead2catagen2026-10-20 11:47 UTC1051
            &user.mirror,
544a170ead2catagen2026-10-20 11:47 UTC1052
            Request::GetAccountData {
544a170ead2catagen2026-10-20 11:47 UTC1053
                event_type: query.event_type,
544a170ead2catagen2026-10-20 11:47 UTC1054
            },
544a170ead2catagen2026-10-20 11:47 UTC1055
        )
544a170ead2catagen2026-10-20 11:47 UTC1056
        .await,
544a170ead2catagen2026-10-20 11:47 UTC1057
    )
544a170ead2catagen2026-10-20 11:47 UTC1058
}
544a170ead2catagen2026-10-20 11:47 UTC1059
ecb8598b38afatagen2026-10-22 09:19 UTC1060
async fn get_url_preview(
ecb8598b38afatagen2026-10-22 09:19 UTC1061
    State(state): State<AppState>,
ecb8598b38afatagen2026-10-22 09:19 UTC1062
    headers: HeaderMap,
ecb8598b38afatagen2026-10-22 09:19 UTC1063
    Query(query): Query<UrlPreviewQuery>,
ecb8598b38afatagen2026-10-22 09:19 UTC1064
) -> Response {
ecb8598b38afatagen2026-10-22 09:19 UTC1065
    let (user, _) = match auth(&state, &headers).await {
ecb8598b38afatagen2026-10-22 09:19 UTC1066
        Ok(pair) => pair,
ecb8598b38afatagen2026-10-22 09:19 UTC1067
        Err(error) => return error.into_response(),
ecb8598b38afatagen2026-10-22 09:19 UTC1068
    };
ecb8598b38afatagen2026-10-22 09:19 UTC1069
    respond(
ecb8598b38afatagen2026-10-22 09:19 UTC1070
        dispatch(
ecb8598b38afatagen2026-10-22 09:19 UTC1071
            &user.handle,
ecb8598b38afatagen2026-10-22 09:19 UTC1072
            &user.mirror,
ecb8598b38afatagen2026-10-22 09:19 UTC1073
            Request::UrlPreview {
ecb8598b38afatagen2026-10-22 09:19 UTC1074
                room_id: query.room_id,
ecb8598b38afatagen2026-10-22 09:19 UTC1075
                url: query.url,
ecb8598b38afatagen2026-10-22 09:19 UTC1076
            },
ecb8598b38afatagen2026-10-22 09:19 UTC1077
        )
ecb8598b38afatagen2026-10-22 09:19 UTC1078
        .await,
ecb8598b38afatagen2026-10-22 09:19 UTC1079
    )
ecb8598b38afatagen2026-10-22 09:19 UTC1080
}
ecb8598b38afatagen2026-10-22 09:19 UTC1081
544a170ead2catagen2026-10-20 11:47 UTC1082
async fn set_account_data(
544a170ead2catagen2026-10-20 11:47 UTC1083
    State(state): State<AppState>,
544a170ead2catagen2026-10-20 11:47 UTC1084
    headers: HeaderMap,
544a170ead2catagen2026-10-20 11:47 UTC1085
    body: axum::Json<AccountDataBody>,
544a170ead2catagen2026-10-20 11:47 UTC1086
) -> Response {
544a170ead2catagen2026-10-20 11:47 UTC1087
    let (user, _) = match auth(&state, &headers).await {
544a170ead2catagen2026-10-20 11:47 UTC1088
        Ok(pair) => pair,
544a170ead2catagen2026-10-20 11:47 UTC1089
        Err(error) => return error.into_response(),
544a170ead2catagen2026-10-20 11:47 UTC1090
    };
544a170ead2catagen2026-10-20 11:47 UTC1091
    let body = body.0;
544a170ead2catagen2026-10-20 11:47 UTC1092
    respond(
544a170ead2catagen2026-10-20 11:47 UTC1093
        dispatch(
544a170ead2catagen2026-10-20 11:47 UTC1094
            &user.handle,
544a170ead2catagen2026-10-20 11:47 UTC1095
            &user.mirror,
544a170ead2catagen2026-10-20 11:47 UTC1096
            Request::SetAccountData {
544a170ead2catagen2026-10-20 11:47 UTC1097
                event_type: body.event_type,
544a170ead2catagen2026-10-20 11:47 UTC1098
                content: body.content,
544a170ead2catagen2026-10-20 11:47 UTC1099
            },
544a170ead2catagen2026-10-20 11:47 UTC1100
        )
544a170ead2catagen2026-10-20 11:47 UTC1101
        .await,
544a170ead2catagen2026-10-20 11:47 UTC1102
    )
544a170ead2catagen2026-10-20 11:47 UTC1103
}
544a170ead2catagen2026-10-20 11:47 UTC1104
973f8fbdaf80atagen2026-09-28 09:10 UTC1105
async fn get_events(State(state): State<AppState>, headers: HeaderMap) -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC1106
    let (user, _) = match auth(&state, &headers).await {
973f8fbdaf80atagen2026-09-28 09:10 UTC1107
        Ok(pair) => pair,
973f8fbdaf80atagen2026-09-28 09:10 UTC1108
        Err(error) => return error.into_response(),
973f8fbdaf80atagen2026-09-28 09:10 UTC1109
    };
973f8fbdaf80atagen2026-09-28 09:10 UTC1110
    let body = subscribe(&user.mirror).map(|event| {
973f8fbdaf80atagen2026-09-28 09:10 UTC1111
        let json = serde_json::to_string(&event).unwrap_or_else(|_| "{\"type\":\"error\"}".into());
973f8fbdaf80atagen2026-09-28 09:10 UTC1112
        Ok::<_, std::convert::Infallible>(SseEvent::default().data(json))
973f8fbdaf80atagen2026-09-28 09:10 UTC1113
    });
973f8fbdaf80atagen2026-09-28 09:10 UTC1114
    Sse::new(body).into_response()
973f8fbdaf80atagen2026-09-28 09:10 UTC1115
}
973f8fbdaf80atagen2026-09-28 09:10 UTC1116
973f8fbdaf80atagen2026-09-28 09:10 UTC1117
async fn get_health() -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC1118
    json_ok(&serde_json::json!({ "status": "ok" }), None)
973f8fbdaf80atagen2026-09-28 09:10 UTC1119
}
973f8fbdaf80atagen2026-09-28 09:10 UTC1120
973f8fbdaf80atagen2026-09-28 09:10 UTC1121
async fn static_shell() -> Response {
973f8fbdaf80atagen2026-09-28 09:10 UTC1122
    (
973f8fbdaf80atagen2026-09-28 09:10 UTC1123
        StatusCode::SERVICE_UNAVAILABLE,
973f8fbdaf80atagen2026-09-28 09:10 UTC1124
        axum::Json(serde_json::json!({ "message": "frontend assets missing; run npm --prefix web run build or use the Nix package" })),
973f8fbdaf80atagen2026-09-28 09:10 UTC1125
    )
973f8fbdaf80atagen2026-09-28 09:10 UTC1126
        .into_response()
973f8fbdaf80atagen2026-09-28 09:10 UTC1127
}
973f8fbdaf80atagen2026-09-28 09:10 UTC1128
973f8fbdaf80atagen2026-09-28 09:10 UTC1129
pub fn router(state: AppState, web_dir: Option<String>) -> Router {
973f8fbdaf80atagen2026-09-28 09:10 UTC1130
    let api = Router::new()
973f8fbdaf80atagen2026-09-28 09:10 UTC1131
        .route("/session", get(get_session))
973f8fbdaf80atagen2026-09-28 09:10 UTC1132
        .route("/login", post(post_login))
973f8fbdaf80atagen2026-09-28 09:10 UTC1133
        .route("/logout", post(post_logout))
973f8fbdaf80atagen2026-09-28 09:10 UTC1134
        .route("/rooms", get(get_rooms))
973f8fbdaf80atagen2026-09-28 09:10 UTC1135
        .route("/events", get(get_events))
973f8fbdaf80atagen2026-09-28 09:10 UTC1136
        .route("/health", get(get_health))
973f8fbdaf80atagen2026-09-28 09:10 UTC1137
        .route("/rooms/open", post(rooms_open))
973f8fbdaf80atagen2026-09-28 09:10 UTC1138
        .route("/rooms/close", post(rooms_close))
973f8fbdaf80atagen2026-09-28 09:10 UTC1139
        .route("/rooms/send", post(rooms_send))
973f8fbdaf80atagen2026-09-28 09:10 UTC1140
        .route("/rooms/paginate", post(rooms_paginate))
824dfd38cdf3atagen2026-10-24 13:43 UTC1141
        .route("/rooms/context", post(rooms_context))
824dfd38cdf3atagen2026-10-24 13:43 UTC1142
        .route("/rooms/context/before", post(rooms_context_before))
824dfd38cdf3atagen2026-10-24 13:43 UTC1143
        .route("/rooms/context/after", post(rooms_context_after))
973f8fbdaf80atagen2026-09-28 09:10 UTC1144
        .route(
973f8fbdaf80atagen2026-09-28 09:10 UTC1145
            "/rooms/upload",
973f8fbdaf80atagen2026-09-28 09:10 UTC1146
            post(rooms_upload).layer(DefaultBodyLimit::max(21 * 1024 * 1024)),
973f8fbdaf80atagen2026-09-28 09:10 UTC1147
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC1148
        .route("/rooms/join", post(rooms_join))
973f8fbdaf80atagen2026-09-28 09:10 UTC1149
        .route("/rooms/create", post(rooms_create))
973f8fbdaf80atagen2026-09-28 09:10 UTC1150
        .route("/rooms/leave", post(rooms_leave))
973f8fbdaf80atagen2026-09-28 09:10 UTC1151
        .route("/rooms/react", post(rooms_react))
cf23dbf4e77catagen2026-10-23 06:36 UTC1152
        .route("/rooms/edit", post(rooms_edit))
973f8fbdaf80atagen2026-09-28 09:10 UTC1153
        .route("/rooms/read", post(rooms_read))
973f8fbdaf80atagen2026-09-28 09:10 UTC1154
        .route("/rooms/typing", post(rooms_typing))
544a170ead2catagen2026-10-20 11:47 UTC1155
        .route(
544a170ead2catagen2026-10-20 11:47 UTC1156
            "/account-data",
544a170ead2catagen2026-10-20 11:47 UTC1157
            get(get_account_data).post(set_account_data),
544a170ead2catagen2026-10-20 11:47 UTC1158
        )
ecb8598b38afatagen2026-10-22 09:19 UTC1159
        .route("/url-preview", get(get_url_preview))
973f8fbdaf80atagen2026-09-28 09:10 UTC1160
        .route("/profile", get(get_profile))
e4c64c36f105atagen2026-10-24 07:09 UTC1161
        .route(
e4c64c36f105atagen2026-10-24 07:09 UTC1162
            "/profile/avatar",
e4c64c36f105atagen2026-10-24 07:09 UTC1163
            post(profile_avatar).layer(DefaultBodyLimit::max(10 * 1024 * 1024)),
e4c64c36f105atagen2026-10-24 07:09 UTC1164
        )
e4c64c36f105atagen2026-10-24 07:09 UTC1165
        .route("/command", post(post_command))
7cca1d5628d1atagen2026-10-24 07:36 UTC1166
        .route(
7cca1d5628d1atagen2026-10-24 07:36 UTC1167
            "/rooms/avatar",
7cca1d5628d1atagen2026-10-24 07:36 UTC1168
            post(rooms_avatar).layer(DefaultBodyLimit::max(10 * 1024 * 1024)),
7cca1d5628d1atagen2026-10-24 07:36 UTC1169
        )
d095131eec0eatagen2026-10-24 08:39 UTC1170
        .route(
d095131eec0eatagen2026-10-24 08:39 UTC1171
            "/media/upload",
d095131eec0eatagen2026-10-24 08:39 UTC1172
            post(media_upload).layer(DefaultBodyLimit::max(10 * 1024 * 1024)),
d095131eec0eatagen2026-10-24 08:39 UTC1173
        )
973f8fbdaf80atagen2026-09-28 09:10 UTC1174
        .route("/media", get(get_media))
973f8fbdaf80atagen2026-09-28 09:10 UTC1175
        .route("/message-media", get(get_message_media))
973f8fbdaf80atagen2026-09-28 09:10 UTC1176
        .route("/spaces/children", get(get_space_children))
973f8fbdaf80atagen2026-09-28 09:10 UTC1177
        .route("/rooms/members", get(get_members))
973f8fbdaf80atagen2026-09-28 09:10 UTC1178
        .route("/encryption", get(get_encryption))
973f8fbdaf80atagen2026-09-28 09:10 UTC1179
        .route("/encryption/recover", post(recover_encryption))
973f8fbdaf80atagen2026-09-28 09:10 UTC1180
        .route("/encryption/start", post(start_verification))
973f8fbdaf80atagen2026-09-28 09:10 UTC1181
        .route("/encryption/verify", post(verify_encryption));
973f8fbdaf80atagen2026-09-28 09:10 UTC1182
    let api = Router::new()
973f8fbdaf80atagen2026-09-28 09:10 UTC1183
        .nest("/api", api)
973f8fbdaf80atagen2026-09-28 09:10 UTC1184
        .layer(middleware::from_fn(csrf_guard));
973f8fbdaf80atagen2026-09-28 09:10 UTC1185
    let router = api.with_state(state).layer(TraceLayer::new_for_http());
973f8fbdaf80atagen2026-09-28 09:10 UTC1186
    match web_dir {
973f8fbdaf80atagen2026-09-28 09:10 UTC1187
        Some(dir) => {
973f8fbdaf80atagen2026-09-28 09:10 UTC1188
            let files =
973f8fbdaf80atagen2026-09-28 09:10 UTC1189
                ServeDir::new(&dir).not_found_service(ServeFile::new(format!("{dir}/index.html")));
973f8fbdaf80atagen2026-09-28 09:10 UTC1190
            router.fallback_service(files)
973f8fbdaf80atagen2026-09-28 09:10 UTC1191
        }
973f8fbdaf80atagen2026-09-28 09:10 UTC1192
        None => router.fallback(static_shell),
973f8fbdaf80atagen2026-09-28 09:10 UTC1193
    }
973f8fbdaf80atagen2026-09-28 09:10 UTC1194
}