/* * tmtd - Suckless To Do list * Copyright (C) 2022 C4TG1RL5 * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ use crate::{task, templates}; use argon2::password_hash::{rand_core::OsRng, SaltString}; use argon2::{Argon2, PasswordHasher}; use sqlx::postgres::{PgConnectOptions, PgConnectionInfo, PgPoolOptions}; use sqlx::Executor; use sqlx::{ConnectOptions, PgPool}; use tracing::info; use tracing::log::LevelFilter; pub struct Database(PgPool); impl Database { pub async fn connect(conn_string: &str) -> anyhow::Result { let mut connect_options: PgConnectOptions = conn_string.parse()?; connect_options.log_statements(LevelFilter::Debug); info!("Connecting to the database"); let pool = PgPoolOptions::new().connect_with(connect_options).await?; let mut conn = pool.acquire().await?; let pgver = conn.server_version_num().map(|v| v.to_string()); info!( "Database connected, PostgreSQL version '{}', migrating schema", pgver.as_deref().unwrap_or("unknown") ); conn.execute(include_str!("sql/schema.sql")).await?; Ok(Self(pool)) } pub fn pool(&self) -> PgPool { self.0.clone() } pub async fn close(&self) { self.0.close().await; } // Async might become a problem here pub async fn get_tasks(&self) -> templates::Tasks { // TODO: actually get the issues from the db based on the category cookies let vec = vec![templates::Task { title: "finish tmtd".to_string(), date: "yesterday".to_string(), // Convert from unix timestamps to // the actual date, also timezone info? status: "assigned".to_string(), assignee: "tmtd contributers".to_string(), description: "DO SOMETHING AAAAAAA".to_string(), id: 1, }]; let logged_in = self.logged_in().await; templates::Tasks { tasks: vec, logged_in } } pub async fn create_task(&self, task: &task::CreateTask) { // TODO: insert the task into the database } pub async fn move_task(&self, task: &task::MoveTask) { // TODO: change the category of the task inside the db } fn hash(&self, password: &str, salt: SaltString) -> Result { let argon2 = Argon2::default(); let hash = argon2.hash_password(password.as_bytes(), &salt); if let Ok(ref hash) = hash { if let Some(ref hash) = hash.hash { return Ok(hash.to_string()); } } tracing::error!("Error hashing password: {:?}", hash); Err(()) } pub async fn register(&self, username: &str, password: &str) { let salt = SaltString::generate(&mut OsRng); let hash = self.hash(password, salt); if let Err(_) = hash { return; } tracing::debug!("{}", hash.unwrap()); // TODO: insert the salt and hash into the DB } pub async fn login(&self, username: &str, password: &str) { // TODO: get the salt from the DB let salt = SaltString::generate(&mut OsRng); let hash = self.hash(password, salt); if let Err(_) = hash { return; } tracing::debug!("{}", hash.unwrap()); // TODO: find user in DB and check if the password matches // TODO: save that the user is logged in a cookie or something } pub async fn logged_in(&self) -> Option { // TODO: find out if the user is logged in and return the username if yes None } }