use crate::permissions::Perm;
use crate::repository::TreePath;
use crate::{config::AcceptJson, get_user_id_strict, Config, Redirect};
use axum::{
debug_handler,
extract::{Form, Path, State},
http::StatusCode,
response::{IntoResponse, Response},
routing::{get, post},
Json, Router,
};
use axum_extra::extract::SignedCookieJar;
use axum_extra::TypedHeader;
use diesel::dsl::now;
use diesel::expression::AsExpression;
use diesel::upsert::excluded;
use diesel::{BoolExpressionMethods, ExpressionMethods, Insertable, OptionalExtension, QueryDsl};
use diesel_async::RunQueryDsl;
use serde_derive::*;
use tracing::*;
pub fn router() -> Router<Config> {
Router::new()
.route("/", get(admin))
.route("/permission", post(perm))
.route("/tag", post(tag))
.fallback(crate::fallback)
}
#[derive(Debug, Serialize)]
struct Admin {
token: Option<String>,
owner: String,
repo: String,
is_private: bool,
permissions: Vec<LoginPerm>,
tags: Vec<Tag>,
login: String,
}
#[derive(Debug, Serialize)]
struct LoginPerm {
login: String,
perm: i64,
}
#[derive(Debug, Serialize)]
struct Tag {
name: String,
color: i32,
id: uuid::Uuid,
}
#[debug_handler]
pub async fn admin(
State(config): State<Config>,
jar: SignedCookieJar,
token: axum_csrf::CsrfToken,
tree: crate::repository::RepoPath,
) -> Result<Response, crate::Error> {
let (uid, login) = crate::get_user_login_strict(&jar, &config).await?;
let mut db = config.db.get().await?;
let id = if let Some(id) = r::repositories
.inner_join(u::users)
.filter(u::login.eq(&tree.owner))
.filter(r::name.eq(&tree.repo))
.filter(u::id.eq(uid))
.select(r::id)
.get_result::<uuid::Uuid>(&mut db)
.await
.optional()?
{
id
} else {
return Ok((StatusCode::FORBIDDEN, "{}").into_response());
};
use crate::db::permissions::dsl as p;
use crate::db::repositories::dsl as r;
use crate::db::tags::dsl as t;
use crate::db::users::dsl as u;
let mut is_private = true;
let permissions = p::permissions
.inner_join(u::users)
.inner_join(r::repositories)
.filter(p::repo_id.eq(id))
.filter(p::end_date.is_null().or(p::end_date.gt(now)))
.select((u::id, u::login, p::perm))
.order_by(p::start_date.desc())
.get_results::<(uuid::Uuid, String, i64)>(&mut db)
.await?
.into_iter()
.filter_map(|(id, login, perm)| {
debug!("{:?} {:?} {:?}", id, login, perm);
if id.is_nil() {
let perm = Perm::from_bits(perm).unwrap();
is_private = !perm.contains(Perm::READ);
None
} else {
Some(LoginPerm { login, perm })
}
})
.collect();
let tags = t::tags
.filter(t::repository_id.eq(id))
.select((t::id, t::name, t::color))
.get_results::<(uuid::Uuid, String, i32)>(&mut db)
.await?
.into_iter()
.map(|(id, name, color)| Tag { id, name, color })
.collect();
let token_ = token.authenticity_token().ok();
Ok((
token,
Json(Admin {
token: token_,
owner: tree.owner,
repo: tree.repo,
is_private,
permissions,
tags,
login,
}),
)
.into_response())
}
#[derive(Debug, Deserialize)]
struct PermForm {
token: String,
#[serde(default)]
login: Option<String>,
}
#[debug_handler]
async fn perm(
State(config): State<Config>,
jar: SignedCookieJar,
token: axum_csrf::CsrfToken,
accept_json: Option<TypedHeader<crate::config::AcceptJson>>,
Path(tree): Path<TreePath>,
body: bytes::Bytes,
) -> Result<Response, crate::Error> {
let form: PermForm = serde_urlencoded::from_bytes(&body)?;
token.verify(&form.token)?;
let mut perm = Perm::empty();
for (k, v) in url::form_urlencoded::parse(&body) {
if k.starts_with("p") && v == "on" {
let (_, b) = k.split_at(1);
if let Some(b) = b.parse().ok().and_then(|x: i32| Perm::from_bits(1 << x)) {
perm |= b
}
}
}
debug!("new {:?}", perm);
let uid = get_user_id_strict(&jar)?;
let mut db = config.db.get().await?;
use crate::db::permissions::dsl as p;
use crate::db::users::dsl as u;
use diesel::sql_types::{BigInt, Uuid};
let (id, _) = crate::repository::repository_id(
&mut db,
&tree.owner,
&tree.repo,
Some(uid),
Perm::EDIT_PERMISSIONS,
)
.await?;
if let Some(l) = form.login {
if l != tree.owner {
u::users
.filter(u::login.eq(&l))
.select((
u::id,
&AsExpression::<Uuid>::as_expression(id),
&AsExpression::<BigInt>::as_expression(perm.bits()),
))
.insert_into(p::permissions)
.into_columns((p::user_id, p::repo_id, p::perm))
.on_conflict((p::user_id, p::repo_id))
.do_update()
.set(p::user_id.eq(excluded(p::user_id)))
.execute(&mut db)
.await?;
}
} else {
diesel::insert_into(p::permissions)
.values((
p::user_id.eq(uuid::Uuid::nil()),
p::repo_id.eq(id),
p::perm.eq(perm.bits()),
))
.on_conflict((p::user_id, p::repo_id))
.do_update()
.set(p::user_id.eq(excluded(p::user_id)))
.execute(&mut db)
.await?;
}
if let Some(TypedHeader(AcceptJson(true))) = accept_json {
Ok(Json(()).into_response())
} else {
Ok(Redirect::to(&format!("/{}/{}/admin", tree.owner, tree.repo)).into_response())
}
}
#[derive(Debug, Deserialize)]
struct TagForm {
token: String,
id: Option<uuid::Uuid>,
tag_name: String,
colorp: i32,
}
#[debug_handler]
async fn tag(
State(config): State<Config>,
jar: SignedCookieJar,
token: axum_csrf::CsrfToken,
accept_json: Option<TypedHeader<crate::config::AcceptJson>>,
Path(tree): Path<TreePath>,
Form(form): Form<TagForm>,
) -> Result<Response, crate::Error> {
token.verify(&form.token)?;
debug!("new {:?}", tree);
let uid = get_user_id_strict(&jar)?;
let mut db = config.db.get().await?;
let (id, _) = crate::repository::repository_id(
&mut db,
&tree.owner,
&tree.repo,
Some(uid),
Perm::EDIT_TAGS,
)
.await?;
use crate::db::tags::dsl as t;
if let Some(tag_id) = form.id {
diesel::update(t::tags.find(tag_id).filter(t::repository_id.eq(id)))
.set((t::name.eq(form.tag_name), t::color.eq(form.colorp)))
.execute(&mut db)
.await?;
} else {
diesel::insert_into(t::tags)
.values((
t::repository_id.eq(id),
t::name.eq(form.tag_name),
t::color.eq(form.colorp),
))
.execute(&mut db)
.await?;
}
if let Some(TypedHeader(AcceptJson(true))) = accept_json {
Ok(Json(()).into_response())
} else {
Ok(Redirect::to(&format!("/{}/{}/admin", tree.owner, tree.repo)).into_response())
}
}