mirror of
https://github.com/perstarkse/minne.git
synced 2026-01-18 07:56:44 +01:00
tailwindcss + wip auth
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@
|
||||
.direnv
|
||||
.devenv
|
||||
database
|
||||
node_modules
|
||||
|
||||
|
||||
|
||||
|
||||
248
Cargo.lock
generated
248
Cargo.lock
generated
@@ -36,6 +36,16 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
@@ -47,6 +57,20 @@ dependencies = [
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"cipher",
|
||||
"ctr",
|
||||
"ghash",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
@@ -176,9 +200,9 @@ checksum = "ea50b14b7a4b9343f8c627a7a53c52076482bd4bdad0a24fd3ec533ed616cc2c"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.89"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
|
||||
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
@@ -516,6 +540,17 @@ dependencies = [
|
||||
"reactor-trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.6"
|
||||
@@ -546,9 +581,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.82"
|
||||
version = "0.1.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
|
||||
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -592,9 +627,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.5"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
|
||||
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
@@ -618,9 +653,9 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 1.0.1",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower 0.5.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -628,9 +663,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.3"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
|
||||
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
@@ -641,7 +676,7 @@ dependencies = [
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper 0.1.2",
|
||||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -658,6 +693,73 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum_session"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219e632039c0e9c7f53f135d7f0aebbfccc39c3a18af2034f789cd1cf0f0a463"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"cookie",
|
||||
"dashmap 6.1.0",
|
||||
"forwarded-header-value",
|
||||
"futures",
|
||||
"hmac",
|
||||
"http",
|
||||
"http-body",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum_session_auth"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "129764f6aecc0e73490db71d50695d9b9f09160b25bc219521210ed605315137"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"axum_session",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"dashmap 6.1.0",
|
||||
"futures",
|
||||
"http",
|
||||
"http-body",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum_session_surreal"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f1fd0c3084f929dd0a3351b7879b8b59f5b3ecd1cac25e93a812e76980dcfb"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum_session",
|
||||
"chrono",
|
||||
"surrealdb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum_typed_multipart"
|
||||
version = "0.12.1"
|
||||
@@ -1148,6 +1250,21 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"base64 0.22.1",
|
||||
"percent-encoding",
|
||||
"rand",
|
||||
"subtle",
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie-factory"
|
||||
version = "0.3.3"
|
||||
@@ -1217,9 +1334,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.10"
|
||||
@@ -1268,6 +1395,20 @@ dependencies = [
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "6.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.6.0"
|
||||
@@ -1605,6 +1746,16 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "forwarded-header-value"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9"
|
||||
dependencies = [
|
||||
"nonempty",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fragile"
|
||||
version = "2.0.0"
|
||||
@@ -1828,6 +1979,16 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
|
||||
dependencies = [
|
||||
"opaque-debug",
|
||||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.0"
|
||||
@@ -2086,7 +2247,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.7",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower 0.4.13",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
@@ -2640,6 +2801,12 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nonempty"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
@@ -2749,6 +2916,12 @@ version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
@@ -3141,6 +3314,18 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
@@ -3538,7 +3723,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 1.0.1",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
@@ -4412,7 +4597,7 @@ dependencies = [
|
||||
"cedar-policy",
|
||||
"chrono",
|
||||
"ciborium",
|
||||
"dashmap",
|
||||
"dashmap 5.5.3",
|
||||
"deunicode",
|
||||
"dmp",
|
||||
"fst",
|
||||
@@ -4515,12 +4700,6 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.1"
|
||||
@@ -4835,6 +5014,21 @@ dependencies = [
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -5119,6 +5313,16 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@@ -5639,8 +5843,12 @@ checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
name = "zettle_db"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-openai",
|
||||
"axum",
|
||||
"axum_session",
|
||||
"axum_session_auth",
|
||||
"axum_session_surreal",
|
||||
"axum_typed_multipart",
|
||||
"futures",
|
||||
"lapin",
|
||||
|
||||
@@ -4,8 +4,12 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.94"
|
||||
async-openai = "0.24.1"
|
||||
axum = { version = "0.7.5", features = ["multipart", "macros"] }
|
||||
axum_session = "0.14.4"
|
||||
axum_session_auth = "0.14.1"
|
||||
axum_session_surreal = "0.2.1"
|
||||
axum_typed_multipart = "0.12.1"
|
||||
futures = "0.3.31"
|
||||
lapin = { version = "2.5.0", features = ["serde_json"] }
|
||||
|
||||
171
package-lock.json
generated
Normal file
171
package-lock.json
generated
Normal file
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"name": "zettle_db",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"daisyui": "^4.12.20"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase-css": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/css-selector-tokenizer": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz",
|
||||
"integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"fastparse": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"cssesc": "bin/cssesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/culori": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz",
|
||||
"integrity": "sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/daisyui": {
|
||||
"version": "4.12.20",
|
||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.20.tgz",
|
||||
"integrity": "sha512-uHr3SQsd2yTjRdVuswTiqGFvZTxX0sGSBRa8JJdbKgmZCk/kRFh4B7Z2jg9vLIdwsHTHPyPgCkZadQo1ce0tAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-selector-tokenizer": "^0.8",
|
||||
"culori": "^3",
|
||||
"picocolors": "^1",
|
||||
"postcss-js": "^4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/daisyui"
|
||||
}
|
||||
},
|
||||
"node_modules/fastparse": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
|
||||
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.49",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
|
||||
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-js": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
|
||||
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"camelcase-css": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12 || ^14 || >= 16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.4.21"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
package.json
Normal file
5
package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"daisyui": "^4.12.20"
|
||||
}
|
||||
}
|
||||
152
src/auth.rs
Normal file
152
src/auth.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
// use crate::{error::ApiError, server::routes::auth::SignupParams, storage::db::SurrealDbClient};
|
||||
// use axum::async_trait;
|
||||
// use axum_session_auth::Authentication;
|
||||
// use serde::{Deserialize, Serialize};
|
||||
// use surrealdb::{
|
||||
// engine::any::Any,
|
||||
// opt::auth::{Database, Namespace, Record},
|
||||
// Object, Surreal,
|
||||
// };
|
||||
// use tracing::info;
|
||||
// use uuid::Uuid;
|
||||
|
||||
// #[derive(Deserialize, Serialize)]
|
||||
// pub struct AuthParams {
|
||||
// email: String,
|
||||
// password: String,
|
||||
// user_id: String,
|
||||
// }
|
||||
|
||||
// #[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
// pub struct User {
|
||||
// pub user_id: String,
|
||||
// pub email: String,
|
||||
// #[serde(default)]
|
||||
// pub anonymous: bool,
|
||||
// }
|
||||
|
||||
// impl Default for User {
|
||||
// fn default() -> Self {
|
||||
// Self {
|
||||
// user_id: "user:guest".into(),
|
||||
// email: "guest@example.com".into(),
|
||||
// anonymous: true,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[async_trait]
|
||||
// impl Authentication<User, i64, Surreal<Any>> for User {
|
||||
// async fn load_user(userid: i64, pool: Option<&Surreal<Any>>) -> Result<User, anyhow::Error> {
|
||||
// let pool = pool.unwrap();
|
||||
// User::get_user(userid, pool)
|
||||
// .await
|
||||
// .ok_or_else(|| anyhow::anyhow!("Could not load user"))
|
||||
// }
|
||||
|
||||
// fn is_authenticated(&self) -> bool {
|
||||
// !self.anonymous
|
||||
// }
|
||||
|
||||
// fn is_active(&self) -> bool {
|
||||
// !self.anonymous
|
||||
// }
|
||||
|
||||
// fn is_anonymous(&self) -> bool {
|
||||
// self.anonymous
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl User {
|
||||
// // pub async fn get_user_by_email(
|
||||
// // email: &str,
|
||||
// // db: &SurrealDbClient,
|
||||
// // ) -> Result<Option<Self>, ApiError> {
|
||||
// // info!("First, let's see what records exist");
|
||||
// // let debug_query: Vec<User> = db.select("users").await?;
|
||||
// // // let debug_query: Vec<User> = db.client.query("SELECT * FROM user").await?.take(0)?;
|
||||
// // info!("All users in database: {:?}", debug_query);
|
||||
|
||||
// // // let tables: Vec<String> = db.client.query("INFO FOR DB").await?.take(0)?;
|
||||
// // // info!("Available tables: {:?}", tables);
|
||||
|
||||
// // // Modified query to match exactly how the record is stored
|
||||
// // let user: Option<User> = db
|
||||
// // .client
|
||||
// // .query("SELECT * FROM user WHERE email = $email LIMIT 1")
|
||||
// // .bind(("email", email.to_string()))
|
||||
// // .await?
|
||||
// // .take(0)?;
|
||||
|
||||
// // info!("Found user: {:?}", user);
|
||||
|
||||
// // Ok(user)
|
||||
// // }
|
||||
|
||||
// pub async fn get_user(id: i64, pool: &Surreal<Any>) -> Option<Self> {
|
||||
// let user: Option<User> = pool
|
||||
// .query("SELECT * FROM user WHERE user_id = $user_id")
|
||||
// .bind(("user_id", format!("user:{}", id)))
|
||||
// .await
|
||||
// .ok()?
|
||||
// .take(0)
|
||||
// .ok()?;
|
||||
|
||||
// user
|
||||
// }
|
||||
|
||||
// pub async fn signin(params: SignupParams, db: &SurrealDbClient) -> Result<(), ApiError> {
|
||||
// info!("Trying to sign in");
|
||||
// let result = db
|
||||
// .client
|
||||
// .signin(Record {
|
||||
// access: "account",
|
||||
// namespace: "test",
|
||||
|
||||
// database: "test",
|
||||
// params: SignupParams {
|
||||
// email: params.email,
|
||||
// password: params.password,
|
||||
// },
|
||||
// })
|
||||
// .await?;
|
||||
|
||||
// info!("{:?}", result.into_insecure_token());
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// pub async fn signup(params: SignupParams, db: &SurrealDbClient) -> Result<Self, ApiError> {
|
||||
// // First check if user already exists
|
||||
// if let Some(_) = Self::get_user_by_email(¶ms.email, db).await? {
|
||||
// return Err(ApiError::UserAlreadyExists);
|
||||
// }
|
||||
|
||||
// // Use SurrealDB's built-in signup
|
||||
// let signup_response = db
|
||||
// .client
|
||||
// .signup(Record {
|
||||
// access: "account",
|
||||
// namespace: "test",
|
||||
// database: "test",
|
||||
// params: AuthParams {
|
||||
// email: params.email.clone(),
|
||||
// password: params.password.clone(),
|
||||
// user_id: Uuid::new_v4().to_string(),
|
||||
// },
|
||||
// })
|
||||
// .await?;
|
||||
|
||||
// info!("Signup response: {:?}", signup_response);
|
||||
|
||||
// // Wait a moment to ensure the record is created
|
||||
// tokio::time::sleep(tokio::time::Duration::from_millis(300)).await;
|
||||
|
||||
// Self::signin(params, db).await?;
|
||||
// // Fetch the created user
|
||||
// // let user = Self::get_user_by_email(¶ms.email, db)
|
||||
// // .await?
|
||||
// // .ok_or(ApiError::UserNotFound)?;
|
||||
|
||||
// Ok(User::default())
|
||||
// }
|
||||
// }
|
||||
@@ -1,23 +1,33 @@
|
||||
use axum::{
|
||||
extract::DefaultBodyLimit,
|
||||
http::Method,
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
use axum_session::{SessionConfig, SessionLayer, SessionStore};
|
||||
use axum_session_auth::{Auth, AuthConfig, AuthSession, AuthSessionLayer, Rights};
|
||||
use axum_session_surreal::SessionSurrealPool;
|
||||
use std::sync::Arc;
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
use tera::Tera;
|
||||
use tower_http::services::ServeDir;
|
||||
use tracing::info;
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
use zettle_db::{
|
||||
rabbitmq::{consumer::RabbitMQConsumer, publisher::RabbitMQProducer, RabbitMQConfig},
|
||||
server::{
|
||||
routes::{
|
||||
file::upload_handler, index::index_handler, ingress::ingress_handler,
|
||||
query::query_handler, queue_length::queue_length_handler,
|
||||
auth::{show_signup_form, signup_handler},
|
||||
file::upload_handler,
|
||||
index::index_handler,
|
||||
ingress::ingress_handler,
|
||||
query::query_handler,
|
||||
queue_length::queue_length_handler,
|
||||
search_result::search_result_handler,
|
||||
},
|
||||
AppState,
|
||||
},
|
||||
storage::db::SurrealDbClient,
|
||||
storage::{db::SurrealDbClient, types::user::User},
|
||||
};
|
||||
|
||||
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -44,11 +54,32 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tera: Arc::new(Tera::new("src/server/templates/**/*.html").unwrap()),
|
||||
openai_client: Arc::new(async_openai::Client::new()),
|
||||
};
|
||||
// app_state.surreal_db_client.query("DELETE user").await?;
|
||||
|
||||
// setup_auth(&app_state.surreal_db_client).await?;
|
||||
|
||||
let session_config = SessionConfig::default()
|
||||
.with_table_name("test_session_table")
|
||||
.with_secure(false);
|
||||
let auth_config = AuthConfig::<String>::default();
|
||||
|
||||
let session_store: SessionStore<SessionSurrealPool<Any>> = SessionStore::new(
|
||||
Some(app_state.surreal_db_client.client.clone().into()),
|
||||
session_config,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Create Axum router
|
||||
let app = Router::new()
|
||||
.nest("/api/v1", api_routes_v1())
|
||||
.nest("", html_routes())
|
||||
.nest(
|
||||
"/",
|
||||
html_routes(
|
||||
session_store,
|
||||
auth_config,
|
||||
app_state.surreal_db_client.client.clone(),
|
||||
),
|
||||
)
|
||||
.with_state(app_state);
|
||||
|
||||
tracing::info!("Listening on 0.0.0.0:3000");
|
||||
@@ -72,9 +103,34 @@ fn api_routes_v1() -> Router<AppState> {
|
||||
}
|
||||
|
||||
/// Router for HTML endpoints
|
||||
fn html_routes() -> Router<AppState> {
|
||||
///
|
||||
fn html_routes(
|
||||
session_store: SessionStore<SessionSurrealPool<Any>>,
|
||||
auth_config: AuthConfig<String>,
|
||||
db_client: Surreal<Any>,
|
||||
) -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(index_handler))
|
||||
.route("/search", get(search_result_handler))
|
||||
.route("/signup", get(show_signup_form).post(signup_handler))
|
||||
.nest_service("/assets", ServeDir::new("src/server/assets"))
|
||||
.layer(
|
||||
AuthSessionLayer::<User, String, SessionSurrealPool<Any>, Surreal<Any>>::new(Some(
|
||||
db_client,
|
||||
))
|
||||
.with_config(auth_config),
|
||||
)
|
||||
.layer(SessionLayer::new(session_store))
|
||||
}
|
||||
|
||||
// async fn setup_auth(db: &SurrealDbClient) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// db.query(
|
||||
// "DEFINE TABLE user SCHEMALESS;
|
||||
// DEFINE INDEX unique_name ON TABLE user FIELDS email UNIQUE;
|
||||
// DEFINE ACCESS account ON DATABASE TYPE RECORD
|
||||
// SIGNUP ( CREATE user SET email = $email, password = crypto::argon2::generate($password), anonymous = false, user_id = $user_id)
|
||||
// SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(password, $password) );",
|
||||
// )
|
||||
// .await?;
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
@@ -60,16 +60,25 @@ pub enum ApiError {
|
||||
OpenAIerror(#[from] OpenAIError),
|
||||
#[error("File error: {0}")]
|
||||
FileError(#[from] FileError),
|
||||
#[error("SurrealDb error: {0}")]
|
||||
SurrealDbError(#[from] surrealdb::Error),
|
||||
#[error("User already exists")]
|
||||
UserAlreadyExists,
|
||||
#[error("User was not found")]
|
||||
UserNotFound,
|
||||
}
|
||||
|
||||
impl IntoResponse for ApiError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let (status, error_message) = match &self {
|
||||
ApiError::ProcessingError(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
|
||||
ApiError::SurrealDbError(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
|
||||
ApiError::PublishingError(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
|
||||
ApiError::DatabaseError(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
|
||||
ApiError::OpenAIerror(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
|
||||
ApiError::QueryError(_) => (StatusCode::BAD_REQUEST, self.to_string()),
|
||||
ApiError::UserAlreadyExists => (StatusCode::BAD_REQUEST, self.to_string()),
|
||||
ApiError::UserNotFound => (StatusCode::BAD_REQUEST, self.to_string()),
|
||||
ApiError::IngressContentError(_) => {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, self.to_string())
|
||||
}
|
||||
|
||||
@@ -10,20 +10,20 @@ use async_openai::types::{
|
||||
ResponseFormatJsonSchema,
|
||||
};
|
||||
use serde_json::json;
|
||||
use surrealdb::engine::remote::ws::Client;
|
||||
use surrealdb::engine::any::Any;
|
||||
use surrealdb::Surreal;
|
||||
use tracing::debug;
|
||||
|
||||
use super::types::llm_analysis_result::LLMGraphAnalysisResult;
|
||||
|
||||
pub struct IngressAnalyzer<'a> {
|
||||
db_client: &'a Surreal<Client>,
|
||||
db_client: &'a Surreal<Any>,
|
||||
openai_client: &'a async_openai::Client<async_openai::config::OpenAIConfig>,
|
||||
}
|
||||
|
||||
impl<'a> IngressAnalyzer<'a> {
|
||||
pub fn new(
|
||||
db_client: &'a Surreal<Client>,
|
||||
db_client: &'a Surreal<Any>,
|
||||
openai_client: &'a async_openai::Client<async_openai::config::OpenAIConfig>,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod auth;
|
||||
pub mod error;
|
||||
pub mod ingress;
|
||||
pub mod rabbitmq;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use surrealdb::{engine::remote::ws::Client, Error, Surreal};
|
||||
use surrealdb::{engine::any::Any, Error, Surreal};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::storage::types::{knowledge_entity::KnowledgeEntity, StoredObject};
|
||||
@@ -33,7 +33,7 @@ use crate::storage::types::{knowledge_entity::KnowledgeEntity, StoredObject};
|
||||
pub async fn find_entities_by_source_ids<T>(
|
||||
source_id: Vec<String>,
|
||||
table_name: String,
|
||||
db_client: &Surreal<Client>,
|
||||
db_client: &Surreal<Any>,
|
||||
) -> Result<Vec<T>, Error>
|
||||
where
|
||||
T: for<'de> serde::Deserialize<'de>,
|
||||
@@ -50,7 +50,7 @@ where
|
||||
|
||||
/// Find entities by their relationship to the id
|
||||
pub async fn find_entities_by_relationship_by_id(
|
||||
db_client: &Surreal<Client>,
|
||||
db_client: &Surreal<Any>,
|
||||
entity_id: String,
|
||||
) -> Result<Vec<KnowledgeEntity>, Error> {
|
||||
let query = format!(
|
||||
@@ -65,7 +65,7 @@ pub async fn find_entities_by_relationship_by_id(
|
||||
|
||||
/// Get a specific KnowledgeEntity by its id
|
||||
pub async fn get_entity_by_id(
|
||||
db_client: &Surreal<Client>,
|
||||
db_client: &Surreal<Any>,
|
||||
entity_id: &str,
|
||||
) -> Result<Option<KnowledgeEntity>, Error> {
|
||||
db_client
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
};
|
||||
use futures::future::{try_join, try_join_all};
|
||||
use std::collections::HashMap;
|
||||
use surrealdb::{engine::remote::ws::Client, Surreal};
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
|
||||
/// Performs a comprehensive knowledge entity retrieval using multiple search strategies
|
||||
/// to find the most relevant entities for a given query.
|
||||
@@ -34,7 +34,7 @@ use surrealdb::{engine::remote::ws::Client, Surreal};
|
||||
/// * `Result<Vec<KnowledgeEntity>, ProcessingError>` - A deduplicated vector of relevant
|
||||
/// knowledge entities, or an error if the retrieval process fails
|
||||
pub async fn combined_knowledge_entity_retrieval(
|
||||
db_client: &Surreal<Client>,
|
||||
db_client: &Surreal<Any>,
|
||||
openai_client: &async_openai::Client<async_openai::config::OpenAIConfig>,
|
||||
query: &str,
|
||||
) -> Result<Vec<KnowledgeEntity>, ProcessingError> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use surrealdb::{engine::remote::ws::Client, Surreal};
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
|
||||
use crate::{error::ProcessingError, utils::embedding::generate_embedding};
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::{error::ProcessingError, utils::embedding::generate_embedding};
|
||||
pub async fn find_items_by_vector_similarity<T>(
|
||||
take: u8,
|
||||
input_text: &str,
|
||||
db_client: &Surreal<Client>,
|
||||
db_client: &Surreal<Any>,
|
||||
table: String,
|
||||
openai_client: &async_openai::Client<async_openai::config::OpenAIConfig>,
|
||||
) -> Result<Vec<T>, ProcessingError>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
36
src/server/routes/auth.rs
Normal file
36
src/server/routes/auth.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use axum::{
|
||||
extract::State,
|
||||
response::{Html, IntoResponse},
|
||||
Form,
|
||||
};
|
||||
use axum_session_auth::AuthSession;
|
||||
use axum_session_surreal::SessionSurrealPool;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
|
||||
use crate::{error::ApiError, server::AppState, storage::types::user::User};
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct SignupParams {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
pub async fn show_signup_form(State(state): State<AppState>) -> Html<String> {
|
||||
let context = tera::Context::new();
|
||||
let html = state
|
||||
.tera
|
||||
.render("auth/signup.html", &context)
|
||||
.unwrap_or_else(|_| "<h1>Error rendering template</h1>".to_string());
|
||||
Html(html)
|
||||
}
|
||||
|
||||
pub async fn signup_handler(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
Form(form): Form<SignupParams>,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
let user = User::create_new(form.email, form.password, &state.surreal_db_client).await?;
|
||||
auth.login_user(user.id);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,13 +1,21 @@
|
||||
use axum::{extract::State, response::Html};
|
||||
use axum_session_auth::AuthSession;
|
||||
use axum_session_surreal::SessionSurrealPool;
|
||||
use serde_json::json;
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
use tera::Context;
|
||||
use tracing::info;
|
||||
|
||||
use crate::{error::ApiError, server::AppState};
|
||||
use crate::{error::ApiError, server::AppState, storage::types::user::User};
|
||||
|
||||
pub async fn index_handler(State(state): State<AppState>) -> Result<Html<String>, ApiError> {
|
||||
pub async fn index_handler(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
) -> Result<Html<String>, ApiError> {
|
||||
info!("Displaying index page");
|
||||
|
||||
info!("{:?}", auth.current_user);
|
||||
|
||||
let queue_length = state.rabbitmq_consumer.get_queue_length().await?;
|
||||
|
||||
let output = state
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod auth;
|
||||
pub mod file;
|
||||
pub mod index;
|
||||
pub mod ingress;
|
||||
|
||||
62
src/server/templates/auth/signup.html
Normal file
62
src/server/templates/auth/signup.html
Normal file
@@ -0,0 +1,62 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="min-h-screen bg-base-200 flex items-center justify-center">
|
||||
<div class="card w-96 bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-2xl font-bold text-center mb-4">Sign Up</h2>
|
||||
|
||||
<form hx-post="/signup" hx-target="#signup-result" class="space-y-4">
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text">Email</span>
|
||||
</label>
|
||||
<input type="text" name="email" placeholder="Enter username" class="input input-bordered w-full" required />
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text">Password</span>
|
||||
</label>
|
||||
<input type="password" name="password" placeholder="Enter password" class="input input-bordered w-full"
|
||||
required />
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-6">
|
||||
<button class="btn btn-primary">
|
||||
Sign Up
|
||||
<span class="loading loading-spinner hidden"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="signup-result"></div>
|
||||
|
||||
<div class="divider">OR</div>
|
||||
|
||||
<div class="text-center text-sm">
|
||||
Already have an account?
|
||||
<a href="/login" class="link link-primary">Login</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add loading indicator when form is submitting -->
|
||||
<script>
|
||||
document.body.addEventListener('htmx:beforeRequest', function (evt) {
|
||||
if (evt.target.tagName === 'FORM') {
|
||||
evt.target.querySelector('.loading-spinner').classList.remove('hidden');
|
||||
evt.target.querySelector('button').disabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
document.body.addEventListener('htmx:afterRequest', function (evt) {
|
||||
if (evt.target.tagName === 'FORM') {
|
||||
evt.target.querySelector('.loading-spinner').classList.add('hidden');
|
||||
evt.target.querySelector('button').disabled = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-theme="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
@@ -9,30 +9,25 @@
|
||||
<script src="https://unpkg.com/htmx.org@2.0.3"></script>
|
||||
</head>
|
||||
|
||||
<body class="min-h-screen bg-gradient-to-br from-slate-50 to-purple-200">
|
||||
<body class="min-h-screen">
|
||||
<!-- Navbar -->
|
||||
<nav class="bg-black/30 backdrop-blur-sm border-b border-white/10">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<div class="flex items-center">
|
||||
<a href="/" class="flex items-center">
|
||||
<!-- You can add your logo here -->
|
||||
<span class="text-2xl hover:text-white font-bold text-gray-300 bg-clip-text">
|
||||
radien
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<a href="/upload"
|
||||
class="text-gray-300 hover:text-white px-3 py-2 rounded-md text-sm font-medium transition-colors">
|
||||
Upload
|
||||
</a>
|
||||
<a href="/files"
|
||||
class="text-gray-300 hover:text-white px-3 py-2 rounded-md text-sm font-medium transition-colors">
|
||||
Files
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="navbar bg-base-200">
|
||||
<div class="flex-1">
|
||||
<a class="btn btn-ghost text-xl">radien</a>
|
||||
</div>
|
||||
<div class="flex-none">
|
||||
<ul class="menu menu-horizontal px-1">
|
||||
<li><a>Link</a></li>
|
||||
<li>
|
||||
<details>
|
||||
<summary>Parent</summary>
|
||||
<ul class="bg-base-100 rounded-t-none p-2">
|
||||
<li><a>Link 1</a></li>
|
||||
<li><a>Link 2</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
@@ -18,26 +18,13 @@
|
||||
|
||||
<!-- Search Bar -->
|
||||
<div class="w-full max-w-2xl">
|
||||
<input type="text" placeholder="Search..." name="query"
|
||||
class="w-full px-6 py-4 bg-black/30 backdrop-blur-md text-white placeholder-gray-400 outline-none rounded-xl"
|
||||
hx-get="/search" hx-target="#search-results">
|
||||
<input type="text" placeholder="Enter your search query" class="input input-bordered w-full" name="query"
|
||||
hx-get="/search" hx-target="#search-results" />
|
||||
</div>
|
||||
|
||||
<!-- Search Results -->
|
||||
<div id="search-results" class="w-full max-w-2xl mt-4">
|
||||
<!-- Results will be populated here by HTMX -->
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="flex gap-4 mt-8">
|
||||
<a href="/upload"
|
||||
class="px-6 py-3 bg-blue-600/20 hover:bg-blue-600/30 border border-blue-500/30 rounded-lg text-blue-400 transition-all hover:scale-105">
|
||||
Upload File
|
||||
</a>
|
||||
<a href="/files"
|
||||
class="px-6 py-3 bg-purple-600/20 hover:bg-purple-600/30 border border-purple-500/30 rounded-lg text-purple-400 transition-all hover:scale-105">
|
||||
Browse Files
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,7 +1,9 @@
|
||||
<div class="h-auto min-h-36 w-full rounded-md bg-gradient-to-r from-pink-500 via-red-500 to-yellow-500 p-0.5">
|
||||
<div class="flex flex-col h-full w-full items-center justify-center bg-gray-800 rounded-md p-4 space-y-3">
|
||||
<p class="font-black text-white text-center break-words">{{result}}</p>
|
||||
<hr class="w-full border-gray-600" />
|
||||
<p class="font-black text-white text-center text-sm">{{references}}</p>
|
||||
<div class="border-">
|
||||
<div class="chat chat-start">
|
||||
<div class="chat-bubble">
|
||||
{{result}}
|
||||
<hr />
|
||||
{{references}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,14 +1,14 @@
|
||||
use super::types::StoredObject;
|
||||
use std::ops::Deref;
|
||||
use surrealdb::{
|
||||
engine::remote::ws::{Client, Ws},
|
||||
engine::any::{connect, Any},
|
||||
opt::auth::Root,
|
||||
Error, Surreal,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SurrealDbClient {
|
||||
pub client: Surreal<Client>,
|
||||
pub client: Surreal<Any>,
|
||||
}
|
||||
|
||||
impl SurrealDbClient {
|
||||
@@ -19,7 +19,7 @@ impl SurrealDbClient {
|
||||
/// # Returns
|
||||
/// * `SurrealDbClient` initialized
|
||||
pub async fn new() -> Result<Self, Error> {
|
||||
let db = Surreal::new::<Ws>("127.0.0.1:8000").await?;
|
||||
let db = connect("ws://127.0.0.1:8000").await?;
|
||||
|
||||
// Sign in to database
|
||||
db.signin(Root {
|
||||
@@ -60,7 +60,7 @@ impl SurrealDbClient {
|
||||
}
|
||||
|
||||
impl Deref for SurrealDbClient {
|
||||
type Target = Surreal<Client>;
|
||||
type Target = Surreal<Any>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.client
|
||||
@@ -75,7 +75,7 @@ impl Deref for SurrealDbClient {
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Result` - Item or Error
|
||||
pub async fn store_item<T>(db_client: &Surreal<Client>, item: T) -> Result<Option<T>, Error>
|
||||
pub async fn store_item<T>(db_client: &Surreal<Any>, item: T) -> Result<Option<T>, Error>
|
||||
where
|
||||
T: StoredObject + Send + Sync + 'static,
|
||||
{
|
||||
@@ -92,7 +92,7 @@ where
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Result` - Vec<T> or Error
|
||||
pub async fn get_all_stored_items<T>(db_client: &Surreal<Client>) -> Result<Vec<T>, Error>
|
||||
pub async fn get_all_stored_items<T>(db_client: &Surreal<Any>) -> Result<Vec<T>, Error>
|
||||
where
|
||||
T: for<'de> StoredObject,
|
||||
{
|
||||
@@ -107,7 +107,7 @@ where
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Result<Option<T>, Error>` - The found item or Error
|
||||
pub async fn get_item<T>(db_client: &Surreal<Client>, id: &str) -> Result<Option<T>, Error>
|
||||
pub async fn get_item<T>(db_client: &Surreal<Any>, id: &str) -> Result<Option<T>, Error>
|
||||
where
|
||||
T: for<'de> StoredObject,
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{error::ProcessingError, stored_object};
|
||||
use surrealdb::{engine::remote::ws::Client, Surreal};
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
use tracing::debug;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -28,7 +28,7 @@ impl KnowledgeRelationship {
|
||||
}
|
||||
pub async fn store_relationship(
|
||||
&self,
|
||||
db_client: &Surreal<Client>,
|
||||
db_client: &Surreal<Any>,
|
||||
) -> Result<(), ProcessingError> {
|
||||
let query = format!(
|
||||
"RELATE knowledge_entity:`{}` -> relates_to -> knowledge_entity:`{}`",
|
||||
|
||||
@@ -5,6 +5,7 @@ pub mod knowledge_entity;
|
||||
pub mod knowledge_relationship;
|
||||
pub mod text_chunk;
|
||||
pub mod text_content;
|
||||
pub mod user;
|
||||
|
||||
#[async_trait]
|
||||
pub trait StoredObject: Serialize + for<'de> Deserialize<'de> {
|
||||
|
||||
100
src/storage/types/user.rs
Normal file
100
src/storage/types/user.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use crate::{
|
||||
error::ApiError,
|
||||
storage::db::{get_item, SurrealDbClient},
|
||||
stored_object,
|
||||
};
|
||||
use axum_session_auth::Authentication;
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
use uuid::Uuid;
|
||||
|
||||
stored_object!(User, "user", {
|
||||
email: String,
|
||||
password: String,
|
||||
anonymous: bool
|
||||
});
|
||||
|
||||
#[async_trait]
|
||||
impl Authentication<User, String, Surreal<Any>> for User {
|
||||
async fn load_user(userid: String, pool: Option<&Surreal<Any>>) -> Result<User, anyhow::Error> {
|
||||
let pool = pool.unwrap();
|
||||
Ok(get_item::<Self>(&pool, userid.as_str()).await?.unwrap())
|
||||
// User::get_user(userid, pool)
|
||||
// .await
|
||||
// .ok_or_else(|| anyhow::anyhow!("Could not load user"))
|
||||
}
|
||||
|
||||
fn is_authenticated(&self) -> bool {
|
||||
!self.anonymous
|
||||
}
|
||||
|
||||
fn is_active(&self) -> bool {
|
||||
!self.anonymous
|
||||
}
|
||||
|
||||
fn is_anonymous(&self) -> bool {
|
||||
self.anonymous
|
||||
}
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub async fn create_new(
|
||||
email: String,
|
||||
password: String,
|
||||
db: &SurrealDbClient,
|
||||
) -> Result<Self, ApiError> {
|
||||
// Check if user exists
|
||||
if let Some(_) = Self::find_by_email(&email, db).await? {
|
||||
return Err(ApiError::UserAlreadyExists);
|
||||
}
|
||||
|
||||
let id = Uuid::new_v4().to_string();
|
||||
let user: Option<User> = db
|
||||
.client
|
||||
.query(
|
||||
"CREATE type::thing('user', $id) SET
|
||||
email = $email,
|
||||
password = crypto::argon2::generate($password),
|
||||
anonymous = false",
|
||||
)
|
||||
.bind(("id", id))
|
||||
.bind(("email", email))
|
||||
.bind(("password", password))
|
||||
.await?
|
||||
.take(0)?;
|
||||
|
||||
user.ok_or(ApiError::UserAlreadyExists)
|
||||
}
|
||||
|
||||
pub async fn authenticate(
|
||||
email: String,
|
||||
password: String,
|
||||
db: &SurrealDbClient,
|
||||
) -> Result<Self, ApiError> {
|
||||
let user: Option<User> = db
|
||||
.client
|
||||
.query(
|
||||
"SELECT * FROM user
|
||||
WHERE email = $email
|
||||
AND crypto::argon2::compare(password, $password)",
|
||||
)
|
||||
.bind(("email", email))
|
||||
.bind(("password", password))
|
||||
.await?
|
||||
.take(0)?;
|
||||
user.ok_or(ApiError::UserAlreadyExists)
|
||||
}
|
||||
|
||||
pub async fn find_by_email(
|
||||
email: &str,
|
||||
db: &SurrealDbClient,
|
||||
) -> Result<Option<Self>, ApiError> {
|
||||
let user: Option<User> = db
|
||||
.client
|
||||
.query("SELECT * FROM user WHERE email = $email LIMIT 1")
|
||||
.bind(("email", email.to_string()))
|
||||
.await?
|
||||
.take(0)?;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,9 @@ module.exports = {
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
plugins: [require('daisyui')],
|
||||
daisyui: {
|
||||
themes: ["dark"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user