13 Commits

Author SHA1 Message Date
e7eb9b8b16 update .gitignore
All checks were successful
Backend Actions / check (push) Successful in 14s
Backend Actions / build (push) Successful in 26s
Backend Actions / test (push) Successful in 35s
2025-07-25 16:28:40 +01:00
726d9c5a09 add scripts for manually packaging and releasing
All checks were successful
Backend Actions / check (push) Successful in 34s
Backend Actions / build (push) Successful in 34s
Backend Actions / test (push) Successful in 42s
2025-07-25 16:25:51 +01:00
7826018d61 Experiment with automatic packaging and releasing
All checks were successful
Backend Actions / check (push) Successful in 12s
Backend Actions / build (push) Successful in 21s
Backend Actions / package (push) Successful in 15s
Backend Actions / test (push) Successful in 29s
use rust-webapp image for release

use npm rather than pnpm

use node 24

use rust latest image

add cache to release

check for tag before releasing

check for tag before releasing

release 0.0.3
2025-07-25 15:35:08 +01:00
16d72f40ce add packaging script
All checks were successful
Backend Actions / check (push) Successful in 15s
Backend Actions / build (push) Successful in 26s
Backend Actions / test (push) Successful in 37s
2025-07-25 00:40:04 +01:00
50cee580de make shutdown endpoint default 2025-07-25 00:39:51 +01:00
fb4bb63335 use nextest image
All checks were successful
Backend Actions / check (push) Successful in 2m17s
Backend Actions / build (push) Successful in 2m28s
Backend Actions / test (push) Successful in 2m54s
2025-07-24 23:49:26 +01:00
f07e688bf1 install specific version
All checks were successful
Backend Actions / check (push) Successful in 22s
Backend Actions / build (push) Successful in 30s
Backend Actions / test (push) Successful in 2m42s
2025-07-24 23:00:04 +01:00
7d1c7a6c75 install nextest
All checks were successful
Backend Actions / check (push) Successful in 14s
Backend Actions / build (push) Successful in 20s
Backend Actions / test (push) Successful in 3m27s
2025-07-24 22:56:06 +01:00
e90ffabf8b check for nextest before running tests
Some checks failed
Backend Actions / test (push) Failing after 1m43s
Backend Actions / build (push) Successful in 2m2s
Backend Actions / check (push) Successful in 39s
2025-07-24 17:42:31 +01:00
346d7b8bcd skip building tests in workflow
All checks were successful
Backend Actions / check (push) Successful in 3m17s
Backend Actions / test (push) Successful in 3m50s
Backend Actions / build (push) Successful in 3m52s
2025-07-24 17:34:35 +01:00
e7107db9f2 fix lints 2025-07-24 17:34:16 +01:00
a69cf540fe refactor admin router into separate file
also set admin secret using command line args
2025-07-24 17:33:35 +01:00
9328451a25 swap to nextest 2025-07-24 17:32:50 +01:00
19 changed files with 14749 additions and 10065 deletions

View File

@ -4,7 +4,7 @@ on: [push]
jobs:
check:
runs-on: rust-latest
runs-on: rust-nextest
defaults:
run:
working-directory: ./backend
@ -23,7 +23,7 @@ jobs:
build:
runs-on: rust-latest
runs-on: rust-nextest
defaults:
run:
working-directory: ./backend
@ -41,7 +41,7 @@ jobs:
run: cargo build --release --locked
test:
runs-on: rust-latest
runs-on: rust-nextest
defaults:
run:
working-directory: ./backend
@ -55,10 +55,8 @@ jobs:
~/.cargo/git
backend/target
key: ${{ runner.os }}-cargo-test-${{ hashFiles('backend/Cargo.lock') }}
- name: Build Binary with Shutdown
run: cargo build --features shutdown --bin nuchat
- name: Build Test Binary
run: cargo test --no-run
- name: Build Binary
run: cargo build --locked --bin nuchat
- name: Run Tests
run: ./scripts/test.sh
- name: Upload Test Logs

2
.gitignore vendored
View File

@ -1 +1,3 @@
.env
package/
*.tar.zst

131
backend/Cargo.lock generated
View File

@ -154,7 +154,7 @@ dependencies = [
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
"windows-targets 0.52.6",
]
[[package]]
@ -346,7 +346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@ -656,9 +656,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.15"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df"
checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
dependencies = [
"base64",
"bytes 1.10.1",
@ -672,7 +672,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
"socket2",
"socket2 0.6.0",
"system-configuration",
"tokio",
"tower-service 0.3.3",
@ -799,9 +799,9 @@ dependencies = [
[[package]]
name = "io-uring"
version = "0.7.8"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
dependencies = [
"bitflags 2.9.1",
"cfg-if 1.0.1",
@ -1195,9 +1195,9 @@ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [
"cfg-if 1.0.1",
"libc",
"redox_syscall 0.5.14",
"redox_syscall 0.5.15",
"smallvec 1.15.1",
"windows-targets",
"windows-targets 0.52.6",
]
[[package]]
@ -1265,9 +1265,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_syscall"
version = "0.5.14"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4"
checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec"
dependencies = [
"bitflags 2.9.1",
]
@ -1395,7 +1395,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@ -1605,6 +1605,16 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "socket2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@ -1722,7 +1732,7 @@ dependencies = [
"pin-project-lite",
"signal-hook-registry",
"slab",
"socket2",
"socket2 0.5.10",
"tokio-macros",
"windows-sys 0.52.0",
]
@ -1872,12 +1882,14 @@ version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [
"base64",
"bitflags 2.9.1",
"bytes 1.10.1",
"futures-util",
"http 1.3.1",
"http-body 1.0.1",
"iri-string",
"mime",
"pin-project-lite",
"tokio",
"tower",
@ -2229,7 +2241,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
"windows-targets 0.52.6",
]
[[package]]
@ -2238,7 +2250,16 @@ version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.2",
]
[[package]]
@ -2247,14 +2268,30 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
dependencies = [
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
]
[[package]]
@ -2263,48 +2300,96 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"

View File

@ -11,7 +11,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.141"
tokio = { version = "1.0", features = ["full"] }
tower = { version = "0.5.2", features = ["full"] }
tower-http = { version = "0.6.6", features = ["timeout", "trace"] }
tower-http = { version = "0.6.6", features = ["timeout", "trace", "auth"] }
tower-http-util = "0.1.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
@ -28,5 +28,6 @@ path = "src/main.rs"
name ="nuchat"
[features]
default = []
default = [ "shutdown" ]
all = ["shutdown"]
shutdown = []

View File

@ -1,11 +1,11 @@
build:
cargo build --features shutdown
cargo build
run:
cargo run --features shutdown
cargo run
start:
cargo run --features shutdown 2>&1 > logs/nuchat.log
cargo run 2>&1 > logs/nuchat.log
test:
./scripts/test.sh
@ -14,6 +14,7 @@ default := 'run'
watch CMD=default:
watchexec -w src -r just {{CMD}}
alias check:=lint
lint *ARGS:
cargo clippy -- -Dwarnings -Dclippy::correctness -Wclippy::pedantic -Wclippy::perf -Aclippy::missing_errors_doc -Aclippy::missing_panics_doc {{ARGS}}
cargo clippy --all-targets -- -Dwarnings -Dclippy::correctness -Wclippy::pedantic -Wclippy::perf -Aclippy::missing_errors_doc -Aclippy::missing_panics_doc {{ARGS}}

View File

@ -1,9 +1,10 @@
# Backend
Generate random secret using. This secret will be used to authenticate users on all `/admin` routes
Generate random secret using openssl. This secret will be used to authenticate users on all `/admin` routes.
If no secret is given then no authentication will be required
```bash
ADMIN_SECRET=$(openssl rand --base64 32)
nuchat --admin-secret $(openssl rand --base64 32)
```
## Features
@ -11,5 +12,5 @@ ADMIN_SECRET=$(openssl rand --base64 32)
### shutdown
this feature enables an endpoint that allows you to shutdown the server.
**WARNING**: If the `ADMIN_SECRET` env var is not set then this endpoint is completely exposed and allows anyone to shutdown the server.
**WARNING**: If the `admin-secret` flag is not set then this endpoint is completely exposed and allows anyone to shutdown the server.

View File

@ -1,5 +1,11 @@
#!/usr/bin/env bash
if ! command -v cargo-nextest > /dev/null 2>&1; then
echo "Command not found cargo-nextest"
echo "Try installing with cargo install cargo-nextest"
exit 1
fi
if [ ! -d logs ]; then
mkdir logs
fi
@ -8,7 +14,7 @@ fi
curl -s -X POST localhost:7001/admin/shutdown 2>&1 > /dev/null
# start server
cargo run --features shutdown -- --port 7001 2>&1 > logs/nuchat.log &
cargo run -- --port 7001 2>&1 > logs/nuchat.log &
# run tests
cargo test | tee logs/test-output.log
cargo nextest run --color=always 2>&1 | tee logs/test-output.log

6
backend/src/config.rs Normal file
View File

@ -0,0 +1,6 @@
#[derive(Default, Clone)]
pub struct Config {
pub port: u32,
pub host: String,
pub admin_secret: Option<String>,
}

View File

@ -1,3 +1,5 @@
mod config;
mod router;
pub use config::Config;
pub use router::app;

View File

@ -1,6 +1,7 @@
use std::sync::mpsc;
use clap::Parser;
use nuchat::Config;
use nuchat::app;
use tokio::net::TcpListener;
use tokio::signal;
@ -17,11 +18,16 @@ struct Args {
/// Host to run server on
#[arg(long, default_value = "127.0.0.1")]
host: String,
/// Admin secret to use, leave blank to disable
#[arg(long)]
admin_secret: Option<String>,
}
#[tokio::main]
async fn main() {
let args = Args::parse();
let config = BinConfig::from_args(args);
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
@ -31,12 +37,12 @@ async fn main() {
.with(tracing_subscriber::fmt::layer().with_target(false))
.init();
let listener = TcpListener::bind(format!("{}:{}", args.host, args.port))
let listener = TcpListener::bind(format!("{}:{}", config.0.host, config.0.port))
.await
.unwrap();
tracing::debug!("listening on {}", listener.local_addr().unwrap());
let (app, rx) = app();
let (app, rx) = app(&config.0);
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal(rx))
.await
@ -77,3 +83,14 @@ async fn shutdown_signal(rx: mpsc::Receiver<bool>) {
}
info!("Shutting server down gracefully...");
}
struct BinConfig(Config);
impl BinConfig {
fn from_args(args: Args) -> Self {
Self(Config {
port: args.port,
host: args.host,
admin_secret: args.admin_secret,
})
}
}

View File

@ -1,27 +1,25 @@
mod admin;
mod healthcheck;
use std::sync::mpsc;
use std::time::Duration;
use crate::config;
use axum::extract::Request;
use axum::middleware::{Next, from_fn};
use axum::response::Response;
#[allow(unused_imports)]
use axum::routing::{get, post};
use axum::routing::get;
use axum::{Router, body::Body};
use http::StatusCode;
use tower::ServiceBuilder;
use tower_http::timeout::TimeoutLayer;
use tower_http::trace::TraceLayer;
use tracing::Level;
use uuid::Uuid;
pub fn app() -> (Router, mpsc::Receiver<bool>) {
pub fn app(config: &config::Config) -> (Router, mpsc::Receiver<bool>) {
let (tx, rx) = mpsc::channel();
(
Router::new()
.route("/healthcheck", get(healthcheck::healthcheck))
.route("/forever", get(std::future::pending::<()>))
.nest("/admin", admin(tx))
.nest("/admin", admin::router(tx, config))
.layer(
ServiceBuilder::new()
.layer(
@ -40,45 +38,3 @@ pub fn app() -> (Router, mpsc::Receiver<bool>) {
rx,
)
}
fn admin(tx: mpsc::Sender<bool>) -> Router {
let r = Router::new().route("/test", get(async || StatusCode::OK));
let r = add_shutdown_endpoint(r, tx);
r.layer(from_fn(async |req: Request, next: Next| {
if let Ok(secret) = std::env::var("ADMIN_SECRET") {
println!("ADMIN_SECRET: {secret}");
match req.headers().get("Authorization") {
Some(key) if secret == *key => (),
_ => {
return Response::builder()
.status(StatusCode::UNAUTHORIZED)
.body(Body::empty())
.unwrap();
}
}
}
next.run(req).await
}))
}
#[cfg(feature = "shutdown")]
fn add_shutdown_endpoint(r: Router, tx: mpsc::Sender<bool>) -> Router {
r.route(
"/shutdown",
post(async move || {
let res = tx.send(true);
if res.is_ok() {
StatusCode::OK
} else {
StatusCode::INTERNAL_SERVER_ERROR
}
}),
)
}
#[cfg(not(feature = "shutdown"))]
fn add_shutdown_endpoint(r: Router, _: mpsc::Sender<bool>) -> Router {
r
}

127
backend/src/router/admin.rs Normal file
View File

@ -0,0 +1,127 @@
use std::sync::mpsc;
use axum::Router;
#[allow(unused_imports)]
use axum::routing::{get, post};
use http::StatusCode;
use tower_http::validate_request::ValidateRequestHeaderLayer;
use tracing::{info, warn};
pub fn router(tx: mpsc::Sender<bool>, config: &crate::Config) -> Router {
let r = Router::new().route("/", get(async || StatusCode::OK));
let r = add_shutdown_endpoint(r, tx);
if let Some(secret) = config.admin_secret.clone() {
info!("Enabled admin authorization");
r.layer(ValidateRequestHeaderLayer::bearer(&secret))
} else {
warn!("Admin authorization disabled");
r
}
}
#[cfg(feature = "shutdown")]
fn add_shutdown_endpoint(r: Router, tx: mpsc::Sender<bool>) -> Router {
r.route(
"/shutdown",
post(async move || {
let res = tx.send(true);
if res.is_ok() {
StatusCode::OK
} else {
StatusCode::INTERNAL_SERVER_ERROR
}
}),
)
}
#[cfg(not(feature = "shutdown"))]
fn add_shutdown_endpoint(r: Router, _: mpsc::Sender<bool>) -> Router {
r
}
#[cfg(test)]
mod test {
use axum::{body::Body, http::Request};
use http::header;
use tower::ServiceExt;
use crate::config;
use super::*;
#[tokio::test]
async fn test_authorization_disables_when_no_secret_set() {
let (tx, _) = mpsc::channel();
let resp = router(tx, &config::Config::default())
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[tokio::test]
async fn test_authorization_unauthorized_no_bearer_token() {
let (tx, _) = mpsc::channel();
let conf = config::Config {
admin_secret: Some(String::from("1234")),
..Default::default()
};
let resp = router(tx, &conf)
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn test_authorization_unauthorized_invalid_bearer_token() {
let (tx, _) = mpsc::channel();
let conf = config::Config {
admin_secret: Some(String::from("1234")),
..Default::default()
};
let resp = router(tx, &conf)
.oneshot(
Request::builder()
.uri("/")
.header(header::AUTHORIZATION, "bearer abcd")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn test_authorization_authorized_valid_bearer_token() {
let (tx, _) = mpsc::channel();
let conf = config::Config {
admin_secret: Some(String::from("1234")),
..Default::default()
};
let resp = router(tx, &conf)
.oneshot(
Request::builder()
.uri("/")
.header(header::AUTHORIZATION, "Bearer 1234")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
}

View File

@ -3,7 +3,7 @@ use std::{sync::LazyLock, time::Duration};
use http::StatusCode;
use reqwest::Client;
static client: LazyLock<Client> = LazyLock::new(|| {
static CLIENT: LazyLock<Client> = LazyLock::new(|| {
Client::builder()
.timeout(Duration::from_secs(10))
.connect_timeout(Duration::from_secs(5))
@ -13,7 +13,7 @@ static client: LazyLock<Client> = LazyLock::new(|| {
#[tokio::test]
async fn test_healthcheck_returns_healthy() {
let resp = client
let resp = CLIENT
.get("http://localhost:7001/healthcheck")
.send()
.await

36
scripts/environ.sh Normal file
View File

@ -0,0 +1,36 @@
#!/usr/bin/env bash
function check_tags() {
TAGS="$(git tag --points-at HEAD)"
if [ -n "$TAGS" ]; then
TAG=$(echo "$TAGS" | grep -E '^v[0-9]+.[0-9]+.[0-9]+$' | sort -V | tail -1)
else
echo "No release tag found"
return 1
fi
}
function package() {
PACKAGE_DIR="${PACKAGE_DIR:-package}"
mkdir "$PACKAGE_DIR" > /dev/null 2>&1
pushd backend
cargo build --release
cp -v target/release/nuchat ../package/
popd
pushd ui
npm ci
npm run build
cp -rv .output ../package/ui
popd
OUT_FILE="${OUT_FILE:-package.tar.zst}"
if [ -f "$OUT_FILE" ]; then
rm "$OUT_FILE" > /dev/null 2>&1
fi
tar -cf - package/ | zstd -19 -T0 -o "${OUT_DIR}${OUT_FILE}"
}

25
scripts/release.sh Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env bash
. ./scripts/environ.sh
check_tags
if [ -n "$TAG" ]; then
echo "Found tag $TAG"
RELEASE_VERSION=$(echo "$TAG" | sed 's/v//')
echo "Releasing $RELEASE_VERSION"
echo ""
else
exit 1
fi
package
if [ ! -f package.tar.zst ]; then
echo "failed to generate package"
exit 1
fi
curl -s -X PUT \
-H "Authorization: token $GITEA_TOKEN" \
--upload-file "$OUT_DIR$OUT_FILE" \
"https://git.molloy.xyz/api/packages/fergus-molloy/generic/nuchat/$RELEASE_VERSION/nuchat.tar.zst"

View File

@ -2,11 +2,5 @@
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: true },
modules: [
'@nuxt/eslint',
'@nuxt/icon',
'@nuxt/image',
'@nuxt/test-utils'
]
modules: ['@nuxt/eslint', '@nuxt/icon', '@nuxt/test-utils']
})

14387
ui/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -11,11 +11,9 @@
},
"dependencies": {
"@nuxt/eslint": "1.6.0",
"@nuxt/icon": "1.15.0",
"@nuxt/image": "1.10.0",
"@nuxt/test-utils": "3.19.2",
"eslint": "^9.31.0",
"nuxt": "^4.0.0",
"@nuxt/icon": "^1.15.0",
"@nuxt/test-utils": "^3.19.2",
"nuxt": "^4.0.1",
"vue": "^3.5.17",
"vue-router": "^4.5.1"
}

9958
ui/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff