You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

211 lines
6.2 KiB

use axum::{
extract::{Path, Query, State},
http::StatusCode,
response::Redirect,
routing::{get, patch},
Extension, Form, Router,
};
use entity::link;
use sea_orm::{entity::prelude::*, ActiveValue, Set};
use tera::Context;
use tracing::error; // use sea_orm::FuckYouForNotKnowingAboutThisTrait;
use crate::{
data::{AppState, CurrentUser, LinkForm},
utils,
};
use std::{collections::HashMap, sync::Arc};
/// crud routes for
pub fn get_routes() -> Router<Arc<AppState>> {
Router::new()
.route("/", get(list).post(|| async { "make a new link" }))
.route(
"/:name",
patch(|| async { "change a link" }).delete(|| async { "yeet a link" }),
)
// admins can change/delete all, users their own
}
pub async fn create(
State(state): State<Arc<AppState>>,
Extension(user): Extension<CurrentUser>,
Form(form): Form<LinkForm>,
) -> Result<Redirect, String> {
// what an utter vomit coffin of a function, wow
// todo: clean this up
let mut tries = 0;
let target = form.target;
let mut source = form.source.unwrap_or(String::new());
match source.as_str() {
"" => loop {
source = utils::gen_id(state.opts.id_len, state.opts.charset.clone());
match try_insert_link(&state.db, source.clone(), target.clone(), user.id).await {
Ok(_) => break,
Err(e) => match e {
DbErr::RecordNotInserted => {
tries += 1;
if tries > 5 {
return Err(utils::render_status_code(
StatusCode::INTERNAL_SERVER_ERROR,
)
.await);
}
continue;
}
_ => {
return Err(
utils::render_status_code(StatusCode::INTERNAL_SERVER_ERROR).await
)
}
},
}
},
_ => match try_insert_link(&state.db, source.clone(), target.clone(), user.id).await {
Ok(_) => (),
Err(e) => {
return Err(utils::render_status_code(match e {
DbErr::RecordNotInserted => StatusCode::BAD_REQUEST,
_ => StatusCode::INTERNAL_SERVER_ERROR,
})
.await)
}
},
}
Ok(Redirect::to(&format!("/gay/link/{}", source)))
}
async fn try_insert_link(
db: &DatabaseConnection,
source: String,
target: String,
userid: i32,
) -> Result<(), DbErr> {
let link = link::ActiveModel {
user_id: ActiveValue::Set(userid),
source: ActiveValue::Set(source),
target: ActiveValue::Set(target),
id: ActiveValue::NotSet,
};
let _res = link::Entity::insert(link).exec(db).await?;
Ok(())
}
pub async fn update(
State(state): State<Arc<AppState>>,
Extension(user): Extension<CurrentUser>,
Path(source): Path<String>,
Form(form): Form<LinkForm>,
) -> Result<Redirect, String> {
let mut link: link::ActiveModel = match link::Entity::find()
.filter(link::Column::Source.eq(source))
.one(&state.db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
.and_then(|l| l.ok_or(StatusCode::NOT_FOUND))
.map_err(|e| utils::render_status_code(e))
{
Ok(l) => l.into(),
Err(f) => Err(f.await)?, // let me do async closures you cowards
};
if (!user.is_admin)
&& link
.user_id
.clone()
.into_value()
.expect("the not null field shouldn't be null probably lmao")
!= Value::from(user.id)
{
return Err(utils::render_status_code(StatusCode::UNAUTHORIZED).await);
}
link.target = Set(form.target);
if let Some(source) = form.source {
link.source = Set(source);
}
let source = link
.source
.clone()
.into_value()
.expect("the field is literally not nullable what the fuck");
match link
.update(&state.db)
.await
.map_err(|_| utils::render_status_code(StatusCode::INTERNAL_SERVER_ERROR))
{
Ok(_) => (),
Err(f) => Err(f.await)?,
};
Ok(Redirect::to(&format!("/gay/link/{}", source)))
}
pub async fn delete(
State(state): State<Arc<AppState>>,
Extension(user): Extension<CurrentUser>,
Path(source): Path<String>,
) -> Result<Redirect, String> {
let link = match link::Entity::find()
.filter(link::Column::Source.eq(source))
.one(&state.db)
.await
{
Err(_) => Err(utils::render_status_code(StatusCode::INTERNAL_SERVER_ERROR).await)?,
Ok(None) => Err(utils::render_status_code(StatusCode::NOT_FOUND).await)?,
Ok(Some(l)) => {
if (!user.is_admin) && l.user_id != user.id {
Err(utils::render_status_code(StatusCode::UNAUTHORIZED).await)?
}
l
},
};
match link.delete(&state.db).await {
Err(_) => Err(utils::render_status_code(StatusCode::INTERNAL_SERVER_ERROR).await)?,
Ok(_) => ()
};
Ok(Redirect::to("/gay/link"))
}
pub async fn list(
State(state): State<Arc<AppState>>,
Query(params): Query<HashMap<String, String>>,
Extension(user): Extension<CurrentUser>,
) -> Result<String, StatusCode> {
let mut query = link::Entity::find();
if !(user.is_admin && params.contains_key("all")) {
query = query.filter(link::Column::UserId.eq(user.id));
}
let links = query
.all(&state.db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let mut context = Context::new();
context.insert("user", &user);
context.insert("links", &links);
Ok(utils::render_template("", Some(context))
.await
.map_err(|e| {
error!("oopsie woopsie, we had a little rendering fucky wucky! {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?)
}
pub async fn access(State(state): State<Arc<AppState>>, Path(source): Path<String>) -> Redirect {
state
.cache
.get(&source)
.map(|target| Redirect::to(&target))
.unwrap_or(Redirect::to("/404"))
}