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.

131 lines
3.9 KiB

use std::fmt::Display;
use argon2::password_hash::rand_core::OsRng;
use argon2::password_hash::SaltString;
use argon2::Argon2;
use argon2::PasswordHash;
use argon2::PasswordHasher;
use argon2::PasswordVerifier;
use axum::http::StatusCode;
use entity::link;
use entity::prelude::*;
use entity::user;
use migration::Migrator;
use migration::MigratorTrait;
use sea_orm::{DatabaseConnection, DbErr};
use sea_orm::entity::prelude::*;
use serde_json::json;
use tera::{Context, Tera};
use tokio::sync::OnceCell;
use tracing::error;
use tracing::info;
pub async fn migrate_and_seed_db(db: &DatabaseConnection) -> Result<(), DbErr> {
Migrator::up(db, None).await?;
match User::find().one(db).await? {
Some(_) => Ok(()),
None => {
User::insert(user::ActiveModel::from_json(json!({
"name": "admin",
"is_admin": true,
"password": hash_password("adminadmin".as_bytes()),
}))?)
.exec(db)
.await?;
info!("created default admin user. log in as admin / no password on the web interface");
Ok(())
}
}
}
pub async fn insert_dev_test_data(db: &DatabaseConnection, charset: Vec<char>, len: usize) -> Result<(), DbErr> {
if User::find().filter(user::Column::Name.eq("dev")).one(db).await?.is_none() {
let dev_password = hash_password("a-password".as_bytes());
let dev_uid = User::insert(user::ActiveModel::from_json(json!({
"name": "dev",
"is_admin": false,
"password": dev_password,
}))?).exec(db).await?.last_insert_id;
info!("created dev user");
let dev_link1 = Link::insert(link::ActiveModel::from_json(json!({
"source": "example",
"target": "http://example.com",
"user_id": dev_uid,
}))?).exec(db).await?.last_insert_id;
let dev_link2 = Link::insert(link::ActiveModel::from_json(json!({
"source": gen_id(len, charset),
"target": "https://random.org",
"user_id": dev_uid,
}))?).exec(db).await?.last_insert_id;
info!("created two links for dev user");
}
Ok(())
}
static TERA: OnceCell<Tera> = OnceCell::const_new();
pub async fn get_tera() -> &'static Tera {
TERA.get_or_init(|| async {
Tera::new("templates/**/*")
.map_err(|e| format!("Error parsing tera template: {}", e))
.unwrap()
})
.await
}
fn get_default_context() -> Context {
todo!()
}
pub async fn render_template(
template_name: &str,
context: Option<Context>,
) -> Result<String, tera::Error> {
let tera = get_tera().await;
let rendered = tera.render(template_name, &context.unwrap_or_else(get_default_context))?;
Ok(rendered)
}
pub async fn render_status_code(sc: StatusCode) -> String {
let tera = get_tera().await;
let mut context = Context::new();
context.insert("status_int", &sc.as_u16());
context.insert(
"status_text",
&sc.canonical_reason()
.unwrap_or("<unknown canonical reason>"),
);
context.insert("status_rendered", &format!("{}", sc));
tera.render("error.html", &context)
.expect("status code template should be infaliible")
}
pub fn hash_password(pw: &[u8]) -> String {
let argon2 = Argon2::default();
let salt = SaltString::generate(&mut OsRng);
argon2
.hash_password(pw, &salt)
.expect("hell is empty")
.to_string()
}
pub fn verify_password(pw: &[u8], hash: &str) -> bool {
let parsed_hash = PasswordHash::new(hash).expect("hash in the database ought to be valid");
Argon2::default().verify_password(pw, &parsed_hash).is_ok()
}
pub fn log_into_status_code(e: impl Display) -> StatusCode {
error!("{}", e);
StatusCode::INTERNAL_SERVER_ERROR
}
pub fn gen_id(len: usize, charset: Vec<char>) -> String {
nanoid::nanoid!(len, &charset)
}