ctoolbox/io/webui/controllers/
graph.rs

1use std::io::Read;
2
3use axum::Form;
4use axum::body::Bytes;
5use axum::extract::{Query, State};
6use axum::response::{IntoResponse, Redirect, Response};
7use axum_typed_multipart::{TryFromMultipart, TypedMultipart};
8use serde::Deserialize;
9
10use crate::io::webui::{
11    AppState, AuthenticatedUser, PageQuery, RequestState, error_400, error_403,
12    respond_page,
13};
14use crate::io::webui::controllers::base::{redirect_temporary};
15use crate::utilities::strtovec;
16use crate::{get_user_and_graph, json_value};
17
18pub async fn get_nodes_index(
19    State(state): State<AppState>,
20    req: RequestState,
21    _q: Query<PageQuery>,
22) -> Response {
23    respond_page(&state, req, "nodes.index", &json_value!({}))
24}
25
26pub async fn get_nodes_view(
27    State(state): State<AppState>,
28    req: RequestState,
29    _q: Query<PageQuery>,
30) -> Response {
31    respond_page(&state, req, "nodes.view", &json_value!({}))
32}
33
34pub async fn get_nodes_create(
35    State(state): State<AppState>,
36    req: RequestState,
37) -> Response {
38    respond_page(&state, req, "nodes.create", &json_value!({}))
39}
40
41#[derive(Deserialize)]
42pub struct CreateNodeForm {
43    graph: u32,
44    node_type: String,
45    node_content: String,
46}
47
48pub async fn post_nodes_create(
49    State(state): State<AppState>,
50    req: RequestState,
51    sess: AuthenticatedUser,
52    Form(input): Form<CreateNodeForm>,
53) -> Response {
54    get_user_and_graph!(&state, &req, sess, input.graph, user, graph);
55
56    if let Err(e) = graph.create_node(
57        &user,
58        input.node_type.as_str(),
59        strtovec(input.node_content.as_str()).as_slice(),
60    ) {
61        return error_400(&state, &req, e);
62    }
63
64    redirect_temporary(req.is_js_request, "/nodes")
65}
66
67pub async fn get_nodes_upload(
68    State(state): State<AppState>,
69    req: RequestState,
70) -> Response {
71    respond_page(&state, req, "nodes.upload", &json_value!({}))
72}
73
74#[derive(TryFromMultipart)]
75#[try_from_multipart(strict)]
76pub struct UploadNodeForm {
77    graph: u32,
78    node_type: String,
79    #[form_data(limit = "unlimited")]
80    node_content: Bytes,
81}
82
83/// Wrapper around axum:body:Bytes that implements Read. Untested.
84struct ReadableBytes {
85    inner: Bytes,
86}
87
88// Untested
89impl Read for ReadableBytes {
90    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
91        let len = std::cmp::min(buf.len(), self.inner.len());
92        buf[..len].copy_from_slice(&self.inner[..len]);
93        self.inner = self.inner.slice(len..);
94        Ok(len)
95    }
96}
97
98impl ReadableBytes {
99    fn new(bytes: Bytes) -> Self {
100        Self { inner: bytes }
101    }
102}
103
104pub async fn post_nodes_upload(
105    State(state): State<AppState>,
106    req: RequestState,
107    sess: AuthenticatedUser,
108    TypedMultipart(form): TypedMultipart<UploadNodeForm>,
109) -> Response {
110    get_user_and_graph!(&state, &req, sess, form.graph, user, graph);
111    let readable_bytes = ReadableBytes::new(form.node_content);
112
113    if let Err(e) =
114        graph.create_node(&user, form.node_type.as_str(), readable_bytes)
115    {
116        return error_400(&state, &req, e);
117    }
118
119    redirect_temporary(req.is_js_request, "/nodes")
120}