2024-02-10 13:31:23 +01:00
|
|
|
use std::str::FromStr;
|
|
|
|
|
2024-02-10 13:07:36 +01:00
|
|
|
use secrecy::ExposeSecret;
|
2024-02-10 13:31:23 +01:00
|
|
|
use sqlx::{query, query_as, sqlite::SqliteConnectOptions, Connection, SqliteConnection};
|
2024-02-10 12:06:53 +01:00
|
|
|
|
2024-02-10 12:32:01 +01:00
|
|
|
use crate::secrets::UserToken;
|
2024-02-10 12:06:53 +01:00
|
|
|
|
|
|
|
// TODO: allow configuring this via envar
|
|
|
|
const DB_URI_DEFAULT: &str = "sqlite://sqlite.db";
|
|
|
|
|
2024-02-10 13:07:36 +01:00
|
|
|
const TABLE_USER_TOKENS: &str = "user_tokens";
|
|
|
|
|
2024-02-10 12:06:53 +01:00
|
|
|
pub trait Database {
|
2024-02-10 12:25:46 +01:00
|
|
|
async fn get_token(&mut self, username: &str) -> Result<UserToken, sqlx::Error>;
|
2024-02-10 13:07:36 +01:00
|
|
|
|
|
|
|
async fn save_token(&mut self, username: &str, token: &UserToken) -> Result<(), sqlx::Error>;
|
2024-02-10 12:06:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct SqliteDatabase {
|
|
|
|
conn: SqliteConnection,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SqliteDatabase {
|
|
|
|
pub async fn open() -> Self {
|
2024-02-10 13:31:23 +01:00
|
|
|
let options = SqliteConnectOptions::from_str(DB_URI_DEFAULT)
|
|
|
|
.expect("Invalid database URI")
|
|
|
|
.create_if_missing(true);
|
|
|
|
|
2024-02-10 12:06:53 +01:00
|
|
|
let mut db = Self {
|
2024-02-10 13:31:23 +01:00
|
|
|
conn: SqliteConnection::connect_with(&options)
|
2024-02-10 12:06:53 +01:00
|
|
|
.await
|
|
|
|
.expect("Failed to open SQLite database"),
|
|
|
|
};
|
|
|
|
|
|
|
|
db.init().await;
|
|
|
|
|
|
|
|
db
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn init(&mut self) {
|
2024-02-10 13:31:23 +01:00
|
|
|
query(&format!(
|
2024-02-10 13:07:36 +01:00
|
|
|
"CREATE TABLE IF NOT EXISTS {TABLE_USER_TOKENS} (
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
2024-02-10 13:18:08 +01:00
|
|
|
username VARCHAR(255) NOT NULL,
|
|
|
|
token NCHAR(30) NOT NULL,
|
|
|
|
valid BOOLEAN NOT NULL,
|
|
|
|
created DATETIME NOT NULL,
|
|
|
|
last_used DATETIME
|
2024-02-10 13:31:23 +01:00
|
|
|
)"
|
|
|
|
))
|
2024-02-10 12:06:53 +01:00
|
|
|
.execute(&mut self.conn)
|
|
|
|
.await
|
|
|
|
.expect("Failed to initialize table user_tokens");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Database for SqliteDatabase {
|
2024-02-10 13:10:54 +01:00
|
|
|
// TODO: distinguish between invalid token and SQLX error
|
2024-02-10 12:25:46 +01:00
|
|
|
async fn get_token(&mut self, username: &str) -> Result<UserToken, sqlx::Error> {
|
|
|
|
let row: (String,) = query_as(
|
|
|
|
"SELECT token
|
|
|
|
FROM user_tokens
|
|
|
|
WHERE
|
2024-02-10 13:07:36 +01:00
|
|
|
username = ?
|
2024-02-10 12:25:46 +01:00
|
|
|
AND valid = TRUE
|
|
|
|
ORDER BY
|
|
|
|
created DESC",
|
|
|
|
)
|
|
|
|
.bind(username)
|
|
|
|
.fetch_one(&mut self.conn)
|
|
|
|
.await?;
|
|
|
|
|
2024-02-10 12:30:04 +01:00
|
|
|
Ok(UserToken::from(row.0))
|
2024-02-10 12:06:53 +01:00
|
|
|
}
|
2024-02-10 13:07:36 +01:00
|
|
|
|
|
|
|
async fn save_token(&mut self, username: &str, token: &UserToken) -> Result<(), sqlx::Error> {
|
2024-02-10 13:31:23 +01:00
|
|
|
query(&format!(
|
2024-02-10 13:07:36 +01:00
|
|
|
"INSERT INTO {TABLE_USER_TOKENS}
|
|
|
|
(username, token, created, valid)
|
|
|
|
VALUES
|
|
|
|
(?, ?, DATETIME('NOW'), TRUE)
|
|
|
|
",
|
2024-02-10 13:31:23 +01:00
|
|
|
))
|
2024-02-10 13:07:36 +01:00
|
|
|
.bind(username)
|
|
|
|
.bind(token.0.expose_secret())
|
|
|
|
.execute(&mut self.conn)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-02-10 12:06:53 +01:00
|
|
|
}
|