basic database
This commit is contained in:
parent
1a3c6f2209
commit
8be8d0ebe1
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug executable 'flabk'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=flabk",
|
||||
"--package=flabk"
|
||||
],
|
||||
"filter": {
|
||||
"name": "flabk",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
// {
|
||||
// "type": "lldb",
|
||||
// "request": "launch",
|
||||
// "name": "Debug unit tests in executable 'flabk'",
|
||||
// "cargo": {
|
||||
// "args": [
|
||||
// "test",
|
||||
// "--no-run",
|
||||
// "--bin=flabk",
|
||||
// "--package=flabk"
|
||||
// ],
|
||||
// "filter": {
|
||||
// "name": "flabk",
|
||||
// "kind": "bin"
|
||||
// }
|
||||
// },
|
||||
// "args": [],
|
||||
// "cwd": "${workspaceFolder}"
|
||||
// }
|
||||
]
|
||||
}
|
|
@ -2,18 +2,71 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base-62"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28ebd71b3e708e895b83ec2d35c6e2ef96e34945706bf4d73826354e84f89b2"
|
||||
dependencies = [
|
||||
"failure",
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
|
@ -66,6 +119,12 @@ version = "1.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
|
@ -108,8 +167,37 @@ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
|
|||
dependencies = [
|
||||
"block-buffer 0.10.3",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"failure_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure_derive"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.8.0"
|
||||
|
@ -124,10 +212,13 @@ name = "flabk"
|
|||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base-62",
|
||||
"handlebars",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tokio-postgres",
|
||||
"warp",
|
||||
]
|
||||
|
||||
|
@ -162,6 +253,17 @@ version = "0.3.24"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.24"
|
||||
|
@ -181,6 +283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
|
@ -209,6 +312,12 @@ dependencies = [
|
|||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.14"
|
||||
|
@ -282,6 +391,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest 0.10.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.8"
|
||||
|
@ -400,6 +518,15 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66b48670c893079d3c2ed79114e3644b7004df1c361a4e0ad52e2e6940d07c3d"
|
||||
dependencies = [
|
||||
"digest 0.10.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
|
@ -422,6 +549,15 @@ dependencies = [
|
|||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.4"
|
||||
|
@ -452,6 +588,36 @@ dependencies = [
|
|||
"twoway",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.1"
|
||||
|
@ -462,6 +628,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.14.0"
|
||||
|
@ -547,6 +722,24 @@ dependencies = [
|
|||
"sha-1 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.0.12"
|
||||
|
@ -579,6 +772,37 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "postgres-protocol"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "878c6cbf956e03af9aa8204b407b9cbf47c072164800aa918c516cd4b056c50c"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"fallible-iterator",
|
||||
"hmac",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"rand",
|
||||
"sha2",
|
||||
"stringprep",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "postgres-types"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73d946ec7d256b04dfadc4e6a3292324e6f417124750fc5c0950f981b703a0f1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fallible-iterator",
|
||||
"postgres-protocol",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
|
@ -657,6 +881,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.11"
|
||||
|
@ -759,6 +989,17 @@ dependencies = [
|
|||
"digest 0.10.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9db03534dff993187064c4e0c05a5708d2a9728ace9a8959b77bedf415dac5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest 0.10.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
|
@ -768,6 +1009,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.7"
|
||||
|
@ -793,6 +1040,22 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stringprep"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.99"
|
||||
|
@ -804,6 +1067,18 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.12.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
|
@ -885,6 +1160,30 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-postgres"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29a12c1b3e0704ae7dfc25562629798b29c72e6b1d0a681b6f29ab4ae5e7f7bf"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"fallible-iterator",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"phf",
|
||||
"pin-project-lite",
|
||||
"postgres-protocol",
|
||||
"postgres-types",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tokio-util 0.7.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.9"
|
||||
|
@ -1040,6 +1339,12 @@ dependencies = [
|
|||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.3.1"
|
||||
|
|
|
@ -7,8 +7,11 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0.64"
|
||||
base-62 = "0.1.1"
|
||||
handlebars = "4.3.3"
|
||||
rand = "0.8.5"
|
||||
serde = { version = "1.0.144", features = ["derive", "std", "serde_derive"]}
|
||||
serde_json = "1.0.85"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-postgres = { version = "0.7.7", features = ["with-serde_json-1"] }
|
||||
warp = { version = "0.3.2" }
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
CREATE TABLE users (
|
||||
id CHAR(22) NOT NULL PRIMARY KEY,
|
||||
username TEXT NOT NULL,
|
||||
host TEXT,
|
||||
display_name TEXT
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX u_username_host ON users (username, host);
|
||||
CREATE UNIQUE INDEX u_username_local ON users (username) WHERE host IS NULL;
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use super::users::Users;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_postgres::{tls::NoTlsStream, Client, Connection, NoTls, Socket};
|
||||
|
||||
const DBERR_UNIQUE: &str = "23505";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DB {
|
||||
client: Arc<Mutex<Client>>,
|
||||
}
|
||||
|
||||
impl DB {
|
||||
pub async fn new(host: String, user: String, database: String) -> Result<Self, anyhow::Error> {
|
||||
let (cl, conn) = tokio_postgres::connect(
|
||||
format!("host={host} user={user} dbname={database}").as_str(),
|
||||
NoTls,
|
||||
)
|
||||
.await?;
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = conn.await {
|
||||
eprintln!("connection error: {}", e);
|
||||
}
|
||||
});
|
||||
let client = Arc::new(Mutex::new(cl));
|
||||
Ok(Self { client })
|
||||
}
|
||||
|
||||
pub fn users(&self) -> Users {
|
||||
Users::new(self.client.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DBError {
|
||||
Duplicate,
|
||||
Other(tokio_postgres::Error),
|
||||
}
|
||||
|
||||
impl From<tokio_postgres::Error> for DBError {
|
||||
fn from(err: tokio_postgres::Error) -> Self {
|
||||
if let Some(code) = err.code() && code.code() == DBERR_UNIQUE {
|
||||
return DBError::Duplicate;
|
||||
}
|
||||
DBError::Other(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
pub mod db;
|
||||
pub mod users;
|
|
@ -0,0 +1,95 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use rand::Rng;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_postgres::{Client, Row};
|
||||
|
||||
use super::db;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Users(Arc<Mutex<Client>>);
|
||||
|
||||
impl Users {
|
||||
pub fn new(client: Arc<Mutex<Client>>) -> Self {
|
||||
Self(client)
|
||||
}
|
||||
|
||||
fn new_id() -> String {
|
||||
let bytes = rand::thread_rng().gen::<[u8; 16]>();
|
||||
base_62::encode(&bytes)
|
||||
}
|
||||
|
||||
pub async fn create_user(&self, u: User) -> Result<User, db::DBError> {
|
||||
let row = self.0.lock().await.query_one(
|
||||
"insert into users (id, username, host, display_name) values ($1, $2, $3, $4) returning id",
|
||||
&[&Self::new_id(), &u.username, &u.host, &u.display_name],
|
||||
).await?;
|
||||
Ok(User {
|
||||
id: row.get("id"),
|
||||
username: u.username,
|
||||
host: u.host,
|
||||
display_name: u.display_name,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn user(&self, by: UserSelect) -> Result<Option<User>, anyhow::Error> {
|
||||
let where_param: String;
|
||||
let where_clause = match by {
|
||||
UserSelect::ID(id) => {
|
||||
where_param = id;
|
||||
"id = $1"
|
||||
}
|
||||
UserSelect::Username(username) => {
|
||||
where_param = username;
|
||||
"username = $1"
|
||||
}
|
||||
UserSelect::FullUsername(full) => {
|
||||
where_param = full;
|
||||
"(username || '@' || host) = $1"
|
||||
}
|
||||
};
|
||||
let rows = self
|
||||
.0
|
||||
.lock()
|
||||
.await
|
||||
.query(
|
||||
format!(
|
||||
"select id, username, host, display_name from users where {}",
|
||||
where_clause
|
||||
)
|
||||
.as_str(),
|
||||
&[&where_param],
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(row) = rows.first() && rows.len() == 1 {
|
||||
Ok(Some(User::from(row)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
pub host: Option<String>,
|
||||
pub display_name: Option<String>,
|
||||
}
|
||||
|
||||
impl From<&Row> for User {
|
||||
fn from(row: &Row) -> Self {
|
||||
Self {
|
||||
id: row.get("id"),
|
||||
username: row.get("username"),
|
||||
host: row.get("host"),
|
||||
display_name: row.get("display_name"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum UserSelect {
|
||||
ID(String),
|
||||
Username(String),
|
||||
FullUsername(String),
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
use warp::hyper::StatusCode;
|
||||
|
||||
pub(crate) fn html_with_status(body: String, status: StatusCode) -> warp::http::Response<String> {
|
||||
warp::http::Response::builder()
|
||||
.header("Content-Type", "text/html; charset=utf-8")
|
||||
.status(status)
|
||||
.body(body)
|
||||
.expect("failed marshalling html response")
|
||||
}
|
||||
|
||||
pub(crate) fn html(body: String) -> warp::http::Response<String> {
|
||||
html_with_status(body, StatusCode::OK)
|
||||
}
|
||||
|
||||
// pub(crate) fn html(body: String) -> Result<warp::http::Response<String>, warp::http::Error> {
|
||||
// warp::http::Response::builder()
|
||||
// .header("Content-Type", "text/html; charset=utf-8")
|
||||
// .status(StatusCode::OK)
|
||||
// .body(body)
|
||||
// }
|
18
src/main.rs
18
src/main.rs
|
@ -1,7 +1,13 @@
|
|||
mod database;
|
||||
mod helpers;
|
||||
mod model;
|
||||
mod servek;
|
||||
mod svc;
|
||||
|
||||
use database::db::DB;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use servek::Server;
|
||||
use servek::servek::Server;
|
||||
use svc::profiles::Profiler;
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum ActivityKind {
|
||||
|
@ -31,7 +37,13 @@ pub struct ObjectLD {
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), anyhow::Error> {
|
||||
Server::new().listen_and_serve(8008).await;
|
||||
|
||||
let db = DB::new(
|
||||
"localhost".to_owned(),
|
||||
"flabk".to_owned(),
|
||||
"flabk".to_owned(),
|
||||
)
|
||||
.await?;
|
||||
let profiler = Profiler::new(db.users());
|
||||
Server::new(profiler).listen_and_serve(8008).await;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct Error<'a> {
|
||||
pub error: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Error<'a> {
|
||||
pub fn error(error: &'a str) -> Self {
|
||||
Self { error }
|
||||
}
|
||||
}
|
116
src/servek.rs
116
src/servek.rs
|
@ -1,116 +0,0 @@
|
|||
use core::panic;
|
||||
use std::{convert::Infallible, fmt::Display};
|
||||
|
||||
use handlebars::{Handlebars, RenderError};
|
||||
use serde::Serialize;
|
||||
use warp::{hyper::StatusCode, reject::Reject, Filter, Rejection, Reply};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Server {
|
||||
hb: Handlebars<'static>,
|
||||
profiler: Profiler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Profiler;
|
||||
|
||||
impl Profiler {
|
||||
fn profile(&self, username: String) -> Result<Profile, Infallible> {
|
||||
Ok(Profile { username })
|
||||
}
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new() -> Self {
|
||||
let mut hb = Handlebars::new();
|
||||
hb.register_template_string("profile", include_str!("../templates/html/profile.html"))
|
||||
.expect("profile template");
|
||||
let profiler = Profiler;
|
||||
|
||||
Self { hb, profiler }
|
||||
}
|
||||
|
||||
pub async fn listen_and_serve(self, port: u16) -> ! {
|
||||
println!("starting server on port {}", port);
|
||||
warp::serve(self.html().recover(Self::handle_rejection))
|
||||
.run(([127, 0, 0, 1], port))
|
||||
.await;
|
||||
panic!("server stopped prematurely")
|
||||
}
|
||||
|
||||
fn html(&self) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
Self::index().or(self.profile())
|
||||
}
|
||||
|
||||
fn index() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::get().and(warp::path::end().map(move || {
|
||||
warp::reply::html(include_str!("../templates/html/index.html").to_owned())
|
||||
}))
|
||||
}
|
||||
|
||||
fn with_server(
|
||||
srv: Server,
|
||||
) -> impl Filter<Extract = (Server,), Error = std::convert::Infallible> + Clone {
|
||||
warp::any().map(move || srv.clone())
|
||||
}
|
||||
|
||||
fn profile(&self) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
warp::get().and(
|
||||
warp::path!("@" / String)
|
||||
.and(Self::with_server(self.clone()))
|
||||
.and_then(|username: String, srv: Server| async move {
|
||||
match srv.hb.render(
|
||||
"profile",
|
||||
&serde_json::json!(srv.profiler.profile(username)?),
|
||||
) {
|
||||
Ok(html) => Ok(warp::reply::html(html)),
|
||||
Err(err) => Err(InternalError::reject(err.to_string())),
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
async fn handle_rejection(err: Rejection) -> Result<impl Reply, Infallible> {
|
||||
if let Some(internal) = err.find::<InternalError>() {
|
||||
println!("internal error: {}", internal);
|
||||
return Ok(warp::reply::with_status(
|
||||
"internal server error",
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
));
|
||||
}
|
||||
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct InternalError {
|
||||
inner: String,
|
||||
}
|
||||
|
||||
impl InternalError {
|
||||
fn reject(err: String) -> Rejection {
|
||||
warp::reject::custom(Self { inner: err })
|
||||
}
|
||||
}
|
||||
|
||||
impl Reject for InternalError {}
|
||||
|
||||
impl From<RenderError> for InternalError {
|
||||
fn from(r: RenderError) -> Self {
|
||||
Self {
|
||||
inner: r.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for InternalError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Profile {
|
||||
username: String,
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use warp::{hyper::Uri, Filter, Rejection, Reply};
|
||||
|
||||
use super::servek::{Server, ServerError};
|
||||
|
||||
impl Server {
|
||||
pub(super) async fn html(
|
||||
&self,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
Self::index()
|
||||
.or(self.profile().await)
|
||||
.or(self.create_profile().await)
|
||||
}
|
||||
|
||||
fn index() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::get().and(warp::path::end().map(move || {
|
||||
warp::reply::html(include_str!("../../templates/html/index.html").to_owned())
|
||||
}))
|
||||
}
|
||||
|
||||
fn with_server(
|
||||
srv: Server,
|
||||
) -> impl Filter<Extract = (Server,), Error = std::convert::Infallible> + Clone {
|
||||
warp::any().map(move || srv.clone())
|
||||
}
|
||||
|
||||
async fn profile(&self) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
warp::get().and(
|
||||
warp::path!("@" / String)
|
||||
.and(Self::with_server(self.clone()))
|
||||
.and_then(|username: String, srv: Server| async move {
|
||||
match srv
|
||||
.hb
|
||||
.render(
|
||||
"profile",
|
||||
&serde_json::json!(srv.profiler.profile(username).await?),
|
||||
)
|
||||
.map(|html| warp::reply::html(html))
|
||||
.map_err(|e| ServerError::from(e).reject_self())
|
||||
{
|
||||
Ok(resp) => Ok(resp),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
async fn create_profile(&self) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
warp::post().and(
|
||||
warp::path!("@" / String)
|
||||
.and(Self::with_server(self.clone()))
|
||||
.and_then(|username: String, srv: Server| async move {
|
||||
let user = srv
|
||||
.profiler
|
||||
.create_user(username, None)
|
||||
.await
|
||||
.map_err(|e| ServerError::from(e));
|
||||
match user {
|
||||
Ok(u) => Ok(warp::redirect(
|
||||
Uri::from_str(format!("/@/{}", u.username).as_str()).unwrap(),
|
||||
)),
|
||||
Err(e) => Err(e.reject_self()),
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
mod html;
|
||||
pub mod servek;
|
|
@ -0,0 +1,111 @@
|
|||
use core::panic;
|
||||
use std::{convert::Infallible, fmt::Display};
|
||||
|
||||
use handlebars::{Handlebars, RenderError};
|
||||
use warp::{hyper::StatusCode, reject::Reject, Filter, Rejection, Reply};
|
||||
|
||||
use crate::{
|
||||
model,
|
||||
svc::profiles::{Profiler, UserError},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Server {
|
||||
pub(super) hb: Handlebars<'static>,
|
||||
pub(super) profiler: Profiler,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new(profiler: Profiler) -> Self {
|
||||
let mut hb = Handlebars::new();
|
||||
hb.register_template_string("profile", include_str!("../../templates/html/profile.html"))
|
||||
.expect("profile template");
|
||||
|
||||
Self { hb, profiler }
|
||||
}
|
||||
|
||||
pub async fn listen_and_serve(self, port: u16) -> ! {
|
||||
println!("starting server on port {}", port);
|
||||
warp::serve(self.html().await.recover(Self::handle_rejection))
|
||||
.run(([127, 0, 0, 1], port))
|
||||
.await;
|
||||
panic!("server stopped prematurely")
|
||||
}
|
||||
|
||||
async fn handle_rejection(err: Rejection) -> Result<impl Reply, Infallible> {
|
||||
let code;
|
||||
let message;
|
||||
|
||||
if err.is_not_found() {
|
||||
code = StatusCode::NOT_FOUND;
|
||||
message = "not found";
|
||||
} else if let Some(err) = err.find::<ServerError>() {
|
||||
match err {
|
||||
ServerError::Internal(err) => {
|
||||
println!("internal server error: {}", err);
|
||||
code = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
message = "internal server error";
|
||||
}
|
||||
ServerError::User(u) => match u {
|
||||
UserError::Duplicate => {
|
||||
code = StatusCode::BAD_REQUEST;
|
||||
message = "duplicate entry";
|
||||
}
|
||||
UserError::Other(_) => {
|
||||
panic!("FIXME: other case should already be handled in conversions")
|
||||
}
|
||||
UserError::NotFound => {
|
||||
code = StatusCode::NOT_FOUND;
|
||||
message = "not found";
|
||||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// We should have expected this... Just log and say its a 500
|
||||
println!("FIXME: unhandled rejection: {:?}", err);
|
||||
code = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
message = "internal server error"
|
||||
}
|
||||
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&model::Error::error(message)),
|
||||
code,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) enum ServerError {
|
||||
Internal(String),
|
||||
User(UserError),
|
||||
}
|
||||
|
||||
impl ServerError {
|
||||
pub(super) fn reject_self(self) -> Rejection {
|
||||
warp::reject::custom(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Reject for ServerError {}
|
||||
|
||||
impl From<RenderError> for ServerError {
|
||||
fn from(r: RenderError) -> Self {
|
||||
Self::Internal(r.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UserError> for ServerError {
|
||||
fn from(u: UserError) -> Self {
|
||||
match u {
|
||||
UserError::Duplicate => Self::User(u),
|
||||
UserError::NotFound => Self::User(u),
|
||||
UserError::Other(o) => Self::Internal(format!("UserError: {}", o)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ServerError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self)
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
pub mod profiles;
|
|
@ -0,0 +1,115 @@
|
|||
use serde::Serialize;
|
||||
use warp::{reject::Reject, Rejection};
|
||||
|
||||
use crate::database::{
|
||||
db,
|
||||
users::{self, UserSelect, Users},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Profiler {
|
||||
db: Users,
|
||||
}
|
||||
|
||||
impl Profiler {
|
||||
pub fn new(db: Users) -> Self {
|
||||
Self { db }
|
||||
}
|
||||
|
||||
pub async fn profile(&self, username: String) -> Result<User, UserError> {
|
||||
let select = if username.contains("@") {
|
||||
UserSelect::FullUsername(username)
|
||||
} else {
|
||||
UserSelect::Username(username)
|
||||
};
|
||||
match self.db.user(select).await? {
|
||||
Some(user) => Ok(User::from(user)),
|
||||
None => Err(UserError::NotFound),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_user(
|
||||
&self,
|
||||
username: String,
|
||||
display_name: Option<String>,
|
||||
) -> Result<User, UserError> {
|
||||
let result = self
|
||||
.db
|
||||
.create_user(
|
||||
User {
|
||||
id: String::new(),
|
||||
username,
|
||||
display_name,
|
||||
host: None,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(User::from(result))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
pub display_name: Option<String>,
|
||||
pub host: Option<String>,
|
||||
}
|
||||
|
||||
impl From<users::User> for User {
|
||||
fn from(u: users::User) -> Self {
|
||||
Self {
|
||||
id: u.id,
|
||||
username: u.username,
|
||||
display_name: u.display_name,
|
||||
host: u.host,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<users::User> for User {
|
||||
fn into(self) -> users::User {
|
||||
users::User {
|
||||
id: self.id,
|
||||
username: self.username,
|
||||
display_name: self.display_name,
|
||||
host: self.host,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum UserError {
|
||||
Duplicate,
|
||||
NotFound,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for UserError {
|
||||
fn from(err: anyhow::Error) -> Self {
|
||||
Self::Other(format!("UserError: {}", err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<db::DBError> for UserError {
|
||||
fn from(err: db::DBError) -> Self {
|
||||
match err {
|
||||
db::DBError::Duplicate => Self::Duplicate,
|
||||
db::DBError::Other(e) => Self::Other(e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for UserError {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Self::Duplicate => String::from("duplicate insert"),
|
||||
Self::NotFound => String::from("not found"),
|
||||
Self::Other(err) => err.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Reject for UserError {}
|
|
@ -0,0 +1,4 @@
|
|||
body {
|
||||
background-color: black;
|
||||
color: rebeccapurple;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>flabk - not found</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>not found</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue