WIP

Nathan Sobo created

Continue adding in more API routes

Change summary

Cargo.lock                |   1 
crates/collab/Cargo.toml  |   3 
crates/collab/src/api.rs  | 125 ++++++++++++++++++----------------------
crates/collab/src/main.rs |  44 ++++++++++---
4 files changed, 93 insertions(+), 80 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -953,6 +953,7 @@ dependencies = [
  "tokio",
  "tokio-tungstenite",
  "toml",
+ "tower",
  "util",
  "workspace",
 ]

crates/collab/Cargo.toml 🔗

@@ -20,7 +20,7 @@ util = { path = "../util" }
 anyhow = "1.0.40"
 async-trait = "0.1.50"
 async-tungstenite = "0.16"
-axum = "0.5"
+axum = { version = "0.5", features = ["json"] }
 base64 = "0.13"
 envy = "0.4.2"
 env_logger = "0.8"
@@ -36,6 +36,7 @@ serde_json = "1.0"
 sha-1 = "0.9"
 tokio = { version = "1", features = ["full"] }
 tokio-tungstenite = "0.17"
+tower = "0.4"
 time = "0.2"
 toml = "0.5.8"
 

crates/collab/src/api.rs 🔗

@@ -1,20 +1,33 @@
-// use crate::{auth, db::UserId, AppState, Request, RequestExt as _};
-use anyhow::Result;
+use crate::{
+    db::{Db, User, UserId},
+    AppState, Result,
+};
+use anyhow::anyhow;
 use axum::{
     body::Body,
-    http::{Request, Response, StatusCode},
-    routing::get,
-    Router,
+    extract::Path,
+    http::{Request, StatusCode},
+    response::{IntoResponse, Response},
+    routing::{get, put},
+    Json, Router,
 };
 use serde::Deserialize;
-use serde_json::json;
 use std::sync::Arc;
 
-use crate::AppState;
-// use surf::StatusCode;
-
-pub fn add_routes(router: Router<Body>) -> Router<Body> {
-    router.route("/users", get(get_users))
+pub fn add_routes(router: Router<Body>, app: Arc<AppState>) -> Router<Body> {
+    router
+        .route("/users", {
+            let app = app.clone();
+            get(move |req| get_users(req, app))
+        })
+        .route("/users", {
+            let app = app.clone();
+            get(move |params| create_user(params, app))
+        })
+        .route("/users/:id", {
+            let app = app.clone();
+            put(move |user_id, params| update_user(user_id, params, app))
+        })
 }
 
 // pub fn add_routes(app: &mut tide::Server<Arc<AppState>>) {
@@ -27,65 +40,48 @@ pub fn add_routes(router: Router<Body>) -> Router<Body> {
 //         .post(create_access_token);
 // }
 
-async fn get_users(request: Request<Body>) -> Result<Response<Body>, (StatusCode, String)> {
+async fn get_users(request: Request<Body>, app: Arc<AppState>) -> Result<Json<Vec<User>>> {
     // request.require_token().await?;
 
-    // let users = request.db().get_all_users().await?;
-
-    // Body::from
-
-    // let body = "Hello World";
-    // Ok(Response::builder()
-    //     .header(CONTENT_LENGTH, body.len() as u64)
-    //     .header(CONTENT_TYPE, "text/plain")
-    //     .body(Body::from(body))?)
-
-    // Ok(tide::Response::builder(StatusCode::Ok)
-    //     .body(tide::Body::from_json(&users)?)
-    //     .build())
-    todo!()
+    let users = app.db.get_all_users().await?;
+    Ok(Json(users))
 }
 
-// async fn get_user(request: Request) -> tide::Result {
-//     request.require_token().await?;
-
-//     let user = request
-//         .db()
-//         .get_user_by_github_login(request.param("github_login")?)
-//         .await?
-//         .ok_or_else(|| surf::Error::from_str(404, "user not found"))?;
-
-//     Ok(tide::Response::builder(StatusCode::Ok)
-//         .body(tide::Body::from_json(&user)?)
-//         .build())
-// }
+#[derive(Deserialize)]
+struct CreateUser {
+    github_login: String,
+    admin: bool,
+}
 
-// async fn create_user(mut request: Request) -> tide::Result {
-//     request.require_token().await?;
+async fn create_user(Json(params): Json<CreateUser>, app: Arc<AppState>) -> Result<Json<User>> {
+    let user_id = app
+        .db
+        .create_user(&params.github_login, params.admin)
+        .await?;
 
-//     #[derive(Deserialize)]
-//     struct Params {
-//         github_login: String,
-//         admin: bool,
-//     }
-//     let params = request.body_json::<Params>().await?;
+    let user = app
+        .db
+        .get_user_by_id(user_id)
+        .await?
+        .ok_or_else(|| anyhow!("couldn't find the user we just created"))?;
 
-//     let user_id = request
-//         .db()
-//         .create_user(&params.github_login, params.admin)
-//         .await?;
+    Ok(Json(user))
+}
 
-//     let user = request.db().get_user_by_id(user_id).await?.ok_or_else(|| {
-//         surf::Error::from_str(
-//             StatusCode::InternalServerError,
-//             "couldn't find the user we just created",
-//         )
-//     })?;
+#[derive(Deserialize)]
+struct UpdateUser {
+    admin: bool,
+}
 
-//     Ok(tide::Response::builder(StatusCode::Ok)
-//         .body(tide::Body::from_json(&user)?)
-//         .build())
-// }
+async fn update_user(
+    Path(user_id): Path<i32>,
+    Json(params): Json<UpdateUser>,
+    app: Arc<AppState>,
+) -> Result<impl IntoResponse> {
+    let user_id = UserId(user_id);
+    app.db.set_user_is_admin(user_id, params.admin).await?;
+    Ok(())
+}
 
 // async fn update_user(mut request: Request) -> tide::Result {
 //     request.require_token().await?;
@@ -94,13 +90,6 @@ async fn get_users(request: Request<Body>) -> Result<Response<Body>, (StatusCode
 //     struct Params {
 //         admin: bool,
 //     }
-//     let user_id = UserId(
-//         request
-//             .param("id")?
-//             .parse::<i32>()
-//             .map_err(|error| surf::Error::from_str(StatusCode::BadRequest, error.to_string()))?,
-//     );
-//     let params = request.body_json::<Params>().await?;
 
 //     request
 //         .db()

crates/collab/src/main.rs 🔗

@@ -5,8 +5,7 @@ mod env;
 mod rpc;
 
 use ::rpc::Peer;
-use anyhow::Result;
-use axum::{body::Body, http::StatusCode, Router};
+use axum::{body::Body, http::StatusCode, response::IntoResponse, Router};
 use db::{Db, PostgresDb};
 
 use serde::Deserialize;
@@ -76,24 +75,16 @@ async fn main() -> Result<()> {
     Ok(())
 }
 
-async fn handle_anyhow_error(err: anyhow::Error) -> (StatusCode, String) {
-    (
-        StatusCode::INTERNAL_SERVER_ERROR,
-        format!("Something went wrong: {}", err),
-    )
-}
-
 pub async fn run_server(
     state: Arc<AppState>,
     peer: Arc<Peer>,
     listener: TcpListener,
 ) -> Result<()> {
     let app = Router::<Body>::new();
-    // TODO: Assign app state to request somehow
     // TODO: Compression on API routes?
     // TODO: Authenticate API routes.
 
-    let app = api::add_routes(app);
+    let app = api::add_routes(app, state);
     // TODO: Add rpc routes
 
     axum::Server::from_tcp(listener)?
@@ -102,3 +93,34 @@ pub async fn run_server(
 
     Ok(())
 }
+
+type Result<T> = std::result::Result<T, Error>;
+
+struct Error(anyhow::Error);
+
+impl<E> From<E> for Error
+where
+    E: Into<anyhow::Error>,
+{
+    fn from(error: E) -> Self {
+        Self(error.into())
+    }
+}
+
+impl IntoResponse for Error {
+    fn into_response(self) -> axum::response::Response {
+        (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", &self.0)).into_response()
+    }
+}
+
+impl std::fmt::Debug for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+impl std::fmt::Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.0.fmt(f)
+    }
+}