summaryrefslogtreecommitdiff
path: root/server/src/main.rs
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2025-01-26 21:58:42 +0100
committerJoel Klinghed <the_jk@spawned.biz>2025-01-26 21:58:42 +0100
commit9e80b8cd1e44fcf863d926055d9fa458db46e0d3 (patch)
tree1fd262ac79127e60d91f9e7efac9702ec3058cdb /server/src/main.rs
parent42334c32226f0ff3248d6d0c7641b7170ca962ce (diff)
Add basic git support
Pushing a commit to a new branch creates a review. Each project has its own git directory, with githooks installed that talkes with server process via unix sockets.
Diffstat (limited to 'server/src/main.rs')
-rw-r--r--server/src/main.rs109
1 files changed, 66 insertions, 43 deletions
diff --git a/server/src/main.rs b/server/src/main.rs
index d70c4e7..d0547c1 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -5,10 +5,10 @@ use futures::{future::TryFutureExt, stream::TryStreamExt};
use rocket::fairing::{self, AdHoc};
use rocket::figment::Figment;
use rocket::http::Status;
-use rocket::response::status::{Conflict, Custom, NotFound};
+use rocket::response::status::{Custom, NotFound};
use rocket::serde::json::Json;
-use rocket::{futures, Build, Rocket};
-use rocket_db_pools::{sqlx, Connection, Database};
+use rocket::{futures, Build, Rocket, State};
+use rocket_db_pools::{sqlx, Connection, Database, Pool};
use sqlx::Acquire;
use std::path::PathBuf;
use utoipa::OpenApi;
@@ -20,6 +20,10 @@ mod tests;
mod api_model;
mod auth;
mod db_utils;
+mod fs_utils;
+mod git;
+mod git_root;
+mod git_socket;
use auth::AuthApiAddon;
@@ -68,20 +72,6 @@ impl From<api_model::UserReviewRole> for u8 {
}
}
-impl TryFrom<u8> for api_model::ReviewState {
- type Error = &'static str;
-
- fn try_from(value: u8) -> Result<Self, Self::Error> {
- match value {
- 0 => Ok(api_model::ReviewState::Draft),
- 1 => Ok(api_model::ReviewState::Open),
- 2 => Ok(api_model::ReviewState::Dropped),
- 3 => Ok(api_model::ReviewState::Closed),
- _ => Err("Invalid review state"),
- }
- }
-}
-
#[utoipa::path(
responses(
(status = 200, description = "Get all projects", body = api_model::Projects),
@@ -132,13 +122,13 @@ async fn projects(
}
async fn get_project(
- db: &mut Connection<Db>,
+ db: &mut <<Db as Database>::Pool as Pool>::Connection,
projectid: &str,
) -> Result<Json<api_model::Project>, NotFound<&'static str>> {
let users = sqlx::query!(
"SELECT id, name, dn, default_role, maintainer FROM users JOIN project_users ON project_users.user=users.id WHERE project_users.project=?",
projectid)
- .fetch(&mut ***db)
+ .fetch(&mut **db)
.map_ok(|r| api_model::ProjectUserEntry {
user: api_model::User {
id: r.id,
@@ -156,7 +146,7 @@ async fn get_project(
"SELECT id,title,description,remote,main_branch FROM projects WHERE id=?",
projectid
)
- .fetch_one(&mut ***db)
+ .fetch_one(&mut **db)
.map_ok(|r| api_model::Project {
id: r.id,
title: r.title,
@@ -182,11 +172,12 @@ async fn get_project(
)]
#[get("/project/<projectid>")]
async fn project(
- mut db: Connection<Db>,
+ db: &Db,
_session: auth::Session,
projectid: &str,
) -> Result<Json<api_model::Project>, NotFound<&'static str>> {
- get_project(&mut db, projectid).await
+ let mut conn = db.get().await.unwrap();
+ get_project(&mut conn, projectid).await
}
#[utoipa::path(
@@ -200,24 +191,30 @@ async fn project(
)]
#[post("/project/<projectid>/new", data = "<data>")]
async fn project_new(
- mut db: Connection<Db>,
+ db: &Db,
+ git_roots_config: &State<git_root::Config<'_>>,
+ roots_state: &State<git_root::Roots>,
session: auth::Session,
projectid: &str,
data: Json<api_model::ProjectData<'_>>,
-) -> Result<Json<api_model::Project>, Conflict<&'static str>> {
+) -> Result<Json<api_model::Project>, Custom<String>> {
+ let remote = data.remote.unwrap_or("");
+ let main_branch = data.main_branch.unwrap_or("main");
+
+ let mut conn = db.get().await.unwrap();
{
- let mut tx = db.begin().await.unwrap();
+ let mut tx = conn.begin().await.unwrap();
sqlx::query!(
"INSERT INTO projects (id, title, description, remote, main_branch) VALUES (?, ?, ?, ?, ?)",
projectid,
data.title.unwrap_or("Unnamed"),
data.description.unwrap_or(""),
- data.remote.unwrap_or(""),
- data.main_branch.unwrap_or("main"),
+ remote,
+ main_branch,
)
.execute(&mut *tx)
- .map_err(|_| Conflict("Project with id already exists"))
+ .map_err(|_| Custom(Status::Conflict, "Project with id already exists".to_string()))
.await?;
sqlx::query!(
@@ -230,7 +227,12 @@ async fn project_new(
tx.commit().await.unwrap();
}
- Ok(get_project(&mut db, projectid).await.unwrap())
+ roots_state
+ .new_project(git_roots_config, db, projectid, remote, main_branch)
+ .map_err(|e| Custom(Status::InternalServerError, e.message))
+ .await?;
+
+ Ok(get_project(&mut conn, projectid).await.unwrap())
}
async fn project_check_maintainer(
@@ -504,25 +506,14 @@ async fn reviews(
}))
}
-#[utoipa::path(
- responses(
- (status = 200, description = "Get review", body = api_model::Review),
- (status = 404, description = "No such review"),
- ),
- security(
- ("session" = []),
- ),
-)]
-#[get("/review/<projectid>/<branch..>")]
-async fn review(
+async fn get_review_from_branch(
mut db: Connection<Db>,
- _session: auth::Session,
projectid: &str,
- branch: PathBuf,
+ branch: &str,
) -> Result<Json<api_model::Review>, NotFound<&'static str>> {
let mut review = sqlx::query!(
"SELECT reviews.id AS id,title,description,state,progress,branch,archived,users.id AS user_id,users.name AS name,users.dn AS user_dn FROM reviews JOIN users ON users.id=owner WHERE project=? AND branch=?",
- projectid, branch.as_path().to_str().unwrap())
+ projectid, branch)
.fetch_one(&mut **db)
.map_ok(|r| {
api_model::Review {
@@ -594,6 +585,36 @@ async fn review(
#[utoipa::path(
responses(
+ (status = 200, description = "Get review", body = api_model::Review),
+ (status = 404, description = "No such review"),
+ ),
+ security(
+ ("session" = []),
+ ),
+)]
+#[get("/review/<projectid>/<branch..>")]
+async fn review(
+ db: Connection<Db>,
+ _session: auth::Session,
+ projectid: &str,
+ branch: PathBuf,
+) -> Result<Json<api_model::Review>, NotFound<&'static str>> {
+ get_review_from_branch(db, projectid, branch.as_path().to_str().unwrap()).await
+}
+
+// Backup for the above. Matches if <branch> ends up encoded (with / as %2f)
+#[get("/review/<projectid>/<branch>", rank = 1)]
+async fn review_encoded_path(
+ db: Connection<Db>,
+ _session: auth::Session,
+ projectid: &str,
+ branch: &str,
+) -> Result<Json<api_model::Review>, NotFound<&'static str>> {
+ get_review_from_branch(db, projectid, branch).await
+}
+
+#[utoipa::path(
+ responses(
(status = 200, description = "Get all users", body = api_model::Users),
),
security(
@@ -672,10 +693,12 @@ fn rocket_from_config(figment: Figment) -> Rocket<Build> {
project_user_del,
reviews,
review,
+ review_encoded_path,
users,
],
)
.attach(auth::stage(basepath))
+ .attach(git_root::stage())
}
#[rocket::main]