mirror of https://github.com/raftario/filite.git
Switch to sled
This commit is contained in:
parent
066231fbd1
commit
81c9246634
|
@ -4,5 +4,3 @@ target/
|
|||
.idea/
|
||||
|
||||
filite.json
|
||||
.env
|
||||
*.db
|
||||
|
|
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
16
Cargo.toml
|
@ -18,19 +18,17 @@ license = "MIT"
|
|||
anyhow = "1.0.32"
|
||||
askama = "0.10.3"
|
||||
base64 = "0.12.3"
|
||||
chrono = { version = "0.4.13", features = ["serde"] }
|
||||
bincode = "1.3.1"
|
||||
chrono = { version = "0.4.18", features = ["serde"] }
|
||||
futures = "0.3.5"
|
||||
rand = "0.7.3"
|
||||
rust-argon2 = "0.8.2"
|
||||
serde = { version = "1.0.115", features = ["derive"] }
|
||||
serde = { version = "1.0.116", features = ["derive"] }
|
||||
serde_json = "1.0.57"
|
||||
sqlx = { version = "0.4.0-beta.1", features = ["chrono", "macros", "migrate", "offline", "runtime-tokio", "sqlite"], default-features = false }
|
||||
structopt = "0.3.16"
|
||||
sled = "0.34.4"
|
||||
structopt = "0.3.18"
|
||||
tokio = { version = "0.2.22", features = ["blocking", "fs", "rt-threaded"] }
|
||||
tracing = "0.1.19"
|
||||
tracing-futures = "0.2.4"
|
||||
tracing-subscriber = "0.2.11"
|
||||
warp = { version = "0.2.4", features = ["tls"], default-features = false }
|
||||
|
||||
[target.'cfg(not(any(target_os = "windows", target_os = "macos")))'.dependencies]
|
||||
openssl = { version = "*", features = ["vendored"] }
|
||||
tracing-subscriber = "0.2.12"
|
||||
warp = { version = "0.2.5", features = ["tls"], default-features = false }
|
||||
|
|
|
@ -6,7 +6,7 @@ COPY ./Cargo.toml ./Cargo.toml
|
|||
COPY ./Cargo.lock ./Cargo.lock
|
||||
|
||||
RUN apk update \
|
||||
&& apk add --no-cache make musl-dev perl \
|
||||
&& apk add --no-cache musl-dev \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
RUN cargo build --release
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
CREATE TABLE users (
|
||||
id varchar(32) NOT NULL PRIMARY KEY,
|
||||
password varchar(256) NOT NULL,
|
||||
role int NOT NULL
|
||||
);
|
|
@ -1,11 +0,0 @@
|
|||
CREATE TABLE filite (
|
||||
id varchar(32) NOT NULL PRIMARY KEY,
|
||||
ty int NOT NULL,
|
||||
val text NOT NULL,
|
||||
|
||||
creator varchar(32) NOT NULL REFERENCES users(id),
|
||||
created timestamp NOT NULL,
|
||||
|
||||
visibility int NOT NULL,
|
||||
views int NOT NULL
|
||||
);
|
353
sqlx-data.json
353
sqlx-data.json
|
@ -1,353 +0,0 @@
|
|||
{
|
||||
"db": "SQLite",
|
||||
"0fa193d147d714b9cbd4efc9f4aef2d0603089dfd0a5a1644db38b2383cb3d75": {
|
||||
"query": "SELECT id, password, role as \"role: Role\" FROM users WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "password",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "role: Role",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"2939cc3de5feaa6024500b980f0bc425cf15ca0e08a47397d2ce56a2d8ca3aaf": {
|
||||
"query": "SELECT id, ty as \"ty: Type\", val, creator, created FROM filite WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "ty: Type",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "val",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "creator",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"ordinal": 4,
|
||||
"type_info": "Datetime"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"2bded90766f48d62b0426cf9fe55477232add7cd45d15f613feada68fc11e80e": {
|
||||
"query": "SELECT id FROM filite WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"5f0027d087a1932b987e697800210f9267b5730e002ef14bf79841ea790c3ab5": {
|
||||
"query": "SELECT * FROM filite WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "ty",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "val",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "creator",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"ordinal": 4,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "visibility",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "views",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"79f48ed4fbf70048313a8ef5b665bc371ed16957e7e7186fb4817029931847ab": {
|
||||
"query": "SELECT id, password, role FROM users WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "password",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "role",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"7fd8490787f6136611e1e30fc87177c7f6ec8ce072539efe6963155107458ba4": {
|
||||
"query": "SELECT id, ty as \"ty: Type\", val, creator, created, visibility as \"visibility: Visibility\" FROM filite WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "ty: Type",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "val",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "creator",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"ordinal": 4,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "visibility: Visibility",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"843923b9a0257cf80f1dff554e7dc8fdfc05f489328e8376513124dfb42996e3": {
|
||||
"query": "SELECT * FROM users WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "password",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "role",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"a0cf6aac396dc7c11b86d885fdb5cbc78c40344ad134a83136d19189d5877538": {
|
||||
"query": "SELECT id, ty as \"ty: Type\", val, creator, created, visibility as \"visibility: Visibility\", views FROM filite WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "ty: Type",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "val",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "creator",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"ordinal": 4,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "visibility: Visibility",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "views",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855": {
|
||||
"query": "",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"e7baf3f2538368ddc02bf7712da5deaf4c19969d69181717033f9b29087ebf8b": {
|
||||
"query": "SELECT id, ty as \"ty: Type\" FROM filite WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "ty: Type",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"fac905277f1bd32c350c4fa7d3636ec79fb74c242f2a3bd4b4e52a10fcd0d0ae": {
|
||||
"query": "UPDATE filite SET views = views + 1 WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
}
|
||||
}
|
71
src/auth.rs
71
src/auth.rs
|
@ -1,21 +1,21 @@
|
|||
use crate::{
|
||||
db,
|
||||
db::models::User,
|
||||
config::{Config, PasswordConfig},
|
||||
db::{self, User},
|
||||
reject::{self, TryExt},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use argon2::Config;
|
||||
use rand::Rng;
|
||||
use sqlx::SqlitePool;
|
||||
use sled::Db;
|
||||
use tokio::task;
|
||||
use warp::{Filter, Rejection};
|
||||
|
||||
pub fn auth_optional(
|
||||
pool: &'static SqlitePool,
|
||||
db: &'static Db,
|
||||
config: &'static Config,
|
||||
) -> impl Filter<Extract = (Option<User>,), Error = Rejection> + Copy + Send + Sync + 'static {
|
||||
warp::header::optional("Authorization").and_then(move |header| async move {
|
||||
match header {
|
||||
Some(h) => match user(h, pool).await {
|
||||
Some(h) => match user(h, db, config).await {
|
||||
Ok(u) => Ok(Some(u)),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
|
@ -25,18 +25,19 @@ pub fn auth_optional(
|
|||
}
|
||||
|
||||
pub fn auth_required(
|
||||
pool: &'static SqlitePool,
|
||||
db: &'static Db,
|
||||
config: &'static Config,
|
||||
) -> impl Filter<Extract = (User,), Error = Rejection> + Copy + Send + Sync + 'static {
|
||||
warp::header::header("Authorization").and_then(move |header| user(header, pool))
|
||||
warp::header::header("Authorization").and_then(move |header| user(header, db, config))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
async fn user(header: String, pool: &SqlitePool) -> Result<User, Rejection> {
|
||||
async fn user(header: String, db: &Db, config: &Config) -> Result<User, Rejection> {
|
||||
if &header[..5] != "Basic" {
|
||||
return Err(reject::unauthorized());
|
||||
}
|
||||
|
||||
let decoded = base64::decode(&header[6..]).or_401()?;
|
||||
let decoded = task::block_in_place(move || base64::decode(&header[6..])).or_401()?;
|
||||
|
||||
let (user, password) = {
|
||||
let mut split = None;
|
||||
|
@ -51,29 +52,49 @@ async fn user(header: String, pool: &SqlitePool) -> Result<User, Rejection> {
|
|||
(std::str::from_utf8(u).or_401()?, p)
|
||||
};
|
||||
|
||||
let user = db::user(user, pool).await.or_500()?.or_401()?;
|
||||
if !verify(user.password.clone(), password.to_owned())
|
||||
.await
|
||||
.or_500()?
|
||||
{
|
||||
let user = db::user(user, db).or_500()?.or_401()?;
|
||||
if !verify(&user.password_hash, password, &config.password).or_500()? {
|
||||
return Err(reject::unauthorized());
|
||||
}
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
// TODO: Allow custom configuration
|
||||
#[tracing::instrument(level = "debug", skip(password))]
|
||||
async fn hash(password: Vec<u8>) -> Result<String> {
|
||||
let config = Config::default();
|
||||
Ok(task::spawn_blocking(move || {
|
||||
let salt: [u8; 16] = rand::thread_rng().gen();
|
||||
argon2::hash_encoded(&password, &salt[..], &config)
|
||||
})
|
||||
.await??)
|
||||
fn hash(password: &[u8], config: &PasswordConfig) -> Result<String> {
|
||||
let mut cfg = argon2::Config::default();
|
||||
if let Some(hl) = config.hash_length {
|
||||
cfg.hash_length = hl;
|
||||
}
|
||||
if let Some(l) = config.lanes {
|
||||
cfg.lanes = l;
|
||||
}
|
||||
if let Some(mc) = config.memory_cost {
|
||||
cfg.mem_cost = mc;
|
||||
}
|
||||
if let Some(tc) = config.time_cost {
|
||||
cfg.time_cost = tc;
|
||||
}
|
||||
if let Some(s) = config.secret.as_ref().map(|s| s.as_bytes()) {
|
||||
cfg.secret = s;
|
||||
}
|
||||
|
||||
let hashed = task::block_in_place(move || {
|
||||
let mut salt = vec![0; config.salt_length.unwrap_or(16)];
|
||||
rand::thread_rng().fill(&mut salt[..]);
|
||||
|
||||
argon2::hash_encoded(password, &salt[..], &cfg)
|
||||
})?;
|
||||
Ok(hashed)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(encoded, password))]
|
||||
async fn verify(encoded: String, password: Vec<u8>) -> Result<bool> {
|
||||
Ok(task::spawn_blocking(move || argon2::verify_encoded(&encoded, &password)).await??)
|
||||
fn verify(encoded: &str, password: &[u8], config: &PasswordConfig) -> Result<bool> {
|
||||
let res = match &config.secret {
|
||||
Some(s) => task::block_in_place(move || {
|
||||
argon2::verify_encoded_ext(encoded, password, s.as_bytes(), &[])
|
||||
})?,
|
||||
None => task::block_in_place(move || argon2::verify_encoded(encoded, password))?,
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
|
|
166
src/config.rs
166
src/config.rs
|
@ -7,45 +7,6 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[inline]
|
||||
fn default_log_level() -> String {
|
||||
"info,sqlx=warn".to_owned()
|
||||
}
|
||||
#[inline]
|
||||
fn log_level_is_default(level: &str) -> bool {
|
||||
level.to_lowercase() == default_log_level()
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(default, rename_all = "kebab-case")]
|
||||
pub struct Config {
|
||||
pub port: u16,
|
||||
pub database_url: String,
|
||||
pub files_dir: PathBuf,
|
||||
#[serde(skip_serializing_if = "log_level_is_default")]
|
||||
pub log_level: String,
|
||||
#[serde(skip_serializing_if = "DefaultExt::is_default")]
|
||||
pub pool: PoolConfig,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tls: Option<TlsConfig>,
|
||||
#[serde(skip_serializing_if = "DefaultExt::is_default")]
|
||||
pub threads: ThreadsConfig,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
port: 80,
|
||||
database_url: "filite.db".to_owned(),
|
||||
files_dir: PathBuf::from("files"),
|
||||
log_level: default_log_level(),
|
||||
tls: None,
|
||||
pool: Default::default(),
|
||||
threads: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(path: impl AsRef<Path>) -> Result<&'static Config> {
|
||||
let file = File::open(path)?;
|
||||
let config: Config = serde_json::from_reader(BufReader::new(file))?;
|
||||
|
@ -59,35 +20,128 @@ pub fn write(path: impl AsRef<Path>) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(default, rename_all = "kebab-case")]
|
||||
pub struct Config {
|
||||
pub port: u16,
|
||||
pub database: DatabaseConfig,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tls: Option<TlsConfig>,
|
||||
#[serde(skip_serializing_if = "DefaultExt::is_default")]
|
||||
pub runtime: RuntimeConfig,
|
||||
#[serde(skip_serializing_if = "DefaultExt::is_default")]
|
||||
pub password: PasswordConfig,
|
||||
#[serde(skip_serializing_if = "Config::log_level_is_default")]
|
||||
pub log_level: String,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
#[inline]
|
||||
fn default_log_level() -> String {
|
||||
"info".to_owned()
|
||||
}
|
||||
#[inline]
|
||||
fn log_level_is_default(level: &String) -> bool {
|
||||
level.to_lowercase() == Self::default_log_level()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
port: 80,
|
||||
database: Default::default(),
|
||||
tls: None,
|
||||
runtime: Default::default(),
|
||||
password: Default::default(),
|
||||
log_level: Self::default_log_level(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct TlsConfig {
|
||||
pub cert: PathBuf,
|
||||
pub key: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(default, rename_all = "kebab-case")]
|
||||
pub struct PoolConfig {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_connections: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub min_connections: Option<u32>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub connect_timeout: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub idle_timeout: Option<u64>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_lifetime: Option<u64>,
|
||||
pub struct DatabaseConfig {
|
||||
pub path: PathBuf,
|
||||
#[serde(default, skip_serializing_if = "DefaultExt::is_default")]
|
||||
pub mode: DatabaseMode,
|
||||
#[serde(
|
||||
default = "DatabaseConfig::default_cache_capacity",
|
||||
skip_serializing_if = "DatabaseConfig::cache_capacity_is_default"
|
||||
)]
|
||||
pub cache_capacity: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, PartialEq)]
|
||||
impl DatabaseConfig {
|
||||
#[inline]
|
||||
fn default_cache_capacity() -> u64 {
|
||||
1024 * 1024 * 1024
|
||||
}
|
||||
#[inline]
|
||||
fn cache_capacity_is_default(cc: &u64) -> bool {
|
||||
*cc == Self::default_cache_capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DatabaseConfig {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
path: PathBuf::from("filite"),
|
||||
mode: Default::default(),
|
||||
cache_capacity: Self::default_cache_capacity(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum DatabaseMode {
|
||||
Space,
|
||||
Throughput,
|
||||
}
|
||||
|
||||
impl From<DatabaseMode> for sled::Mode {
|
||||
#[inline]
|
||||
fn from(m: DatabaseMode) -> Self {
|
||||
match m {
|
||||
DatabaseMode::Space => Self::LowSpace,
|
||||
DatabaseMode::Throughput => Self::HighThroughput,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DatabaseMode {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::Space
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(default, rename_all = "kebab-case")]
|
||||
pub struct ThreadsConfig {
|
||||
pub struct RuntimeConfig {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub core_threads: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_threads: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(default, rename_all = "kebab-case")]
|
||||
pub struct PasswordConfig {
|
||||
pub hash_length: Option<u32>,
|
||||
pub salt_length: Option<usize>,
|
||||
pub lanes: Option<u32>,
|
||||
pub memory_cost: Option<u32>,
|
||||
pub time_cost: Option<u32>,
|
||||
pub secret: Option<String>,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
use crate::config::DatabaseConfig;
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sled::Db;
|
||||
use tokio::task;
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub fn connect(config: &DatabaseConfig) -> Result<&'static Db> {
|
||||
let db = sled::Config::default()
|
||||
.path(&config.path)
|
||||
.mode(config.mode.into())
|
||||
.cache_capacity(config.cache_capacity)
|
||||
.open()?;
|
||||
Ok(Box::leak(Box::new(db)))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct User {
|
||||
pub admin: bool,
|
||||
pub password_hash: String,
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(db))]
|
||||
pub fn user(id: &str, db: &Db) -> Result<Option<User>> {
|
||||
task::block_in_place(move || {
|
||||
let users = db.open_tree("users")?;
|
||||
let bytes = match users.get(id)? {
|
||||
Some(b) => b,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let user = bincode::deserialize(&bytes)?;
|
||||
Ok(Some(user))
|
||||
})
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
pub mod models;
|
||||
pub mod pool;
|
||||
|
||||
use crate::db::models::{Filite, Role, Type, User, Visibility};
|
||||
use anyhow::Result;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub async fn user(id: &str, pool: &SqlitePool) -> Result<Option<User>> {
|
||||
Ok(sqlx::query_as!(
|
||||
User,
|
||||
r#"SELECT id, password, role as "role: Role" FROM users WHERE id = $1"#,
|
||||
id
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub async fn filite(id: &str, view: bool, pool: &SqlitePool) -> Result<Option<Filite>> {
|
||||
if !view
|
||||
|| sqlx::query!("UPDATE filite SET views = views + 1 WHERE id = $1", id)
|
||||
.fetch_optional(pool)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
Ok(sqlx::query_as!(
|
||||
Filite,
|
||||
r#"SELECT id, ty as "ty: Type", val, creator, created, visibility as "visibility: Visibility", views FROM filite WHERE id = $1"#,
|
||||
id
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await?)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
use chrono::NaiveDateTime;
|
||||
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub password: String,
|
||||
pub role: Role,
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type)]
|
||||
#[repr(i64)]
|
||||
pub enum Role {
|
||||
User = 0,
|
||||
Admin = 255,
|
||||
}
|
||||
|
||||
pub struct Filite {
|
||||
pub id: String,
|
||||
pub ty: Type,
|
||||
pub val: String,
|
||||
|
||||
pub creator: String,
|
||||
pub created: NaiveDateTime,
|
||||
|
||||
pub visibility: Visibility,
|
||||
pub views: i64,
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type)]
|
||||
#[repr(i64)]
|
||||
pub enum Type {
|
||||
Fi = 0,
|
||||
Li = 1,
|
||||
Te = 2,
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type)]
|
||||
#[repr(i64)]
|
||||
pub enum Visibility {
|
||||
Public = 0,
|
||||
Protected = 1,
|
||||
Private = 2,
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
use crate::config::Config;
|
||||
use anyhow::Result;
|
||||
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
|
||||
use std::time::Duration;
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub async fn build(config: &Config) -> Result<&'static SqlitePool> {
|
||||
let mut options: SqlitePoolOptions = Default::default();
|
||||
|
||||
if let Some(ms) = config.pool.max_connections {
|
||||
options = options.max_connections(ms);
|
||||
}
|
||||
if let Some(ms) = config.pool.min_connections {
|
||||
options = options.min_connections(ms);
|
||||
}
|
||||
|
||||
if let Some(ct) = config.pool.connect_timeout {
|
||||
options = options.connect_timeout(Duration::from_millis(ct));
|
||||
}
|
||||
if let Some(it) = config.pool.idle_timeout {
|
||||
options = options.idle_timeout(Duration::from_millis(it));
|
||||
}
|
||||
|
||||
if let Some(ml) = config.pool.max_lifetime {
|
||||
options = options.max_lifetime(Duration::from_millis(ml));
|
||||
}
|
||||
|
||||
let pool = options
|
||||
.connect(&format!("sqlite://{}", config.database_url))
|
||||
.await?;
|
||||
sqlx::migrate!("./migrations").run(&pool).await?;
|
||||
|
||||
Ok(&*Box::leak(Box::new(pool)))
|
||||
}
|
|
@ -51,14 +51,15 @@ fn main() -> Result<(), Error> {
|
|||
.with_span_events(FmtSpan::CLOSE)
|
||||
.init();
|
||||
|
||||
let mut runtime = runtime::build(&config)?;
|
||||
let db = db::connect(&config.database)?;
|
||||
|
||||
let mut runtime = runtime::build(&config.runtime)?;
|
||||
runtime.block_on(run(config))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run(config: &'static Config) -> Result<(), Error> {
|
||||
let pool = db::pool::build(&config).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
use crate::config::Config;
|
||||
use crate::config::RuntimeConfig;
|
||||
use anyhow::Error;
|
||||
use tokio::runtime::{Builder, Runtime};
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub fn build(config: &Config) -> Result<Runtime, Error> {
|
||||
pub fn build(config: &RuntimeConfig) -> Result<Runtime, Error> {
|
||||
let mut builder = Builder::new();
|
||||
builder.threaded_scheduler().enable_all();
|
||||
|
||||
let config = &config.threads;
|
||||
if let Some(ct) = config.core_threads {
|
||||
builder.core_threads(ct);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue