super basic application with communication between front and backend

This commit is contained in:
Rachel Lambda Samuelsson 2024-08-01 11:52:21 +02:00
parent 8255663005
commit afc03bb722
11 changed files with 385 additions and 159 deletions

1
back/.gitignore vendored
View File

@ -1 +1,2 @@
/target
db.sqlite

109
back/Cargo.lock generated
View File

@ -477,6 +477,12 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "hex"
version = "0.4.3"
@ -734,6 +740,16 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
@ -781,6 +797,16 @@ dependencies = [
"libm",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "object"
version = "0.36.1"
@ -796,6 +822,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking_lot"
version = "0.12.3"
@ -904,7 +936,10 @@ name = "plantback"
version = "0.1.0"
dependencies = [
"axum",
"serde",
"sqlx",
"tokio",
"tracing-subscriber",
]
[[package]]
@ -1111,6 +1146,15 @@ dependencies = [
"digest",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "signature"
version = "2.2.0"
@ -1451,6 +1495,16 @@ dependencies = [
"syn 2.0.71",
]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "tinyvec"
version = "1.8.0"
@ -1476,6 +1530,7 @@ dependencies = [
"bytes",
"libc",
"mio",
"num_cpus",
"pin-project-lite",
"socket2",
"tokio-macros",
@ -1562,6 +1617,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"nu-ansi-term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
]
[[package]]
@ -1626,6 +1707,12 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "vcpkg"
version = "0.2.15"
@ -1660,6 +1747,28 @@ dependencies = [
"wasite",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"

View File

@ -5,4 +5,7 @@ edition = "2021"
[dependencies]
axum = "0.7.5"
serde = "1.0.204"
sqlx = { version = "0.7.4", features = ["runtime-tokio", "sqlite"] }
tokio = { version = "1.38.1", features = ["rt-multi-thread", "macros", "net", "time", "sync"] }
tracing-subscriber = "0.3.18"

View File

@ -1,3 +1,76 @@
fn main() {
println!("Hello, world!");
use std::str::FromStr;
use axum::{
extract::State, http::StatusCode, routing::post, Json, Router
};
use sqlx::{Row, sqlite::{SqliteConnectOptions, SqlitePoolOptions}, Pool, Sqlite};
#[derive(Clone)]
struct ST {
db: Pool<Sqlite>
}
#[tokio::main]
async fn main() {
const ADDR : &str = "localhost:3013";
const DB_ADDR : &str = "sqlite://./db.sqlite";
tracing_subscriber::fmt::init();
let listener = tokio::net::TcpListener::bind(ADDR)
.await.expect(&format!("Could not bind on {ADDR}"));
println!("Listening on http://{ADDR}");
let opts = SqliteConnectOptions::from_str(DB_ADDR)
.expect("Could not parse database address")
.create_if_missing(true);
let db = SqlitePoolOptions::new()
.max_connections(5)
.connect_with(opts)
.await.expect("Could not open database");
println!("Opened sqlite database on {DB_ADDR}");
sqlx::query(r#"
CREATE TABLE IF NOT EXISTS Counter (
id INTEGER PRIMARY KEY check (id = 0),
value INTEGER NOT NULL
);
INSERT OR IGNORE INTO Counter (id, value) VALUES (0, 0);
"#).execute(&db).await.expect("Could not initialize database");
let state : ST = ST { db };
let app = Router::new()
.route("/api/echo", post(echo))
.route("/api/get_counter", post(get_counter))
.route("/api/set_counter", post(set_counter))
.with_state(state);
axum::serve(listener, app)
.await.expect("Unable to start app");
}
async fn get_counter(State(ST { db }): State<ST>) -> (StatusCode, String) {
let row = sqlx::query("SELECT * FROM Counter")
.fetch_one(&db)
.await.expect("no counter in db");
let response : u32 = row.get("value");
(StatusCode::OK, response.to_string())
}
async fn set_counter(State(ST { db }): State<ST>, Json(body) : Json<String>) -> (StatusCode, String) {
sqlx::query(match &*body {
"add" => "UPDATE Counter SET value = value + 1",
"sub" => "UPDATE Counter SET value = value - 1",
_ => return (StatusCode::BAD_REQUEST, "invalid action".to_string()),
}).execute(&db).await.expect("database write error");
get_counter(State(ST { db })).await
}
async fn echo(body : String) -> (StatusCode, String) {
(StatusCode::OK, body)
}

View File

@ -49,7 +49,7 @@
'';
devShells.default = pkgs.mkShell {
shellHook = ''
PATH="${builtins.toString ./front/node_modules/.bin}:$PATH"
PATH="$PWD/front/node_modules/.bin:$PATH"
'';
packages = deps;
};

180
front/package-lock.json generated
View File

@ -11,6 +11,7 @@
"vue": "^3.4.29"
},
"devDependencies": {
"@rachelambda/well-rested": "^0.1.3",
"@tsconfig/node20": "^20.1.4",
"@types/node": "^20.14.5",
"@vitejs/plugin-vue": "^5.0.5",
@ -23,6 +24,7 @@
"typescript-language-server": "^4.3.3",
"vite": "^5.3.1",
"vite-plugin-pages": "^0.32.3",
"vue-router": "^4.4.1",
"vue-tsc": "^2.0.21"
}
},
@ -506,6 +508,13 @@
"node": ">= 8"
}
},
"node_modules/@rachelambda/well-rested": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@rachelambda/well-rested/-/well-rested-0.1.3.tgz",
"integrity": "sha512-r8v+GI9zLZztk69uqUiAd2Ubz3bp6QDCXh0CAdGic7jm5ikMkiolv//C1Rtlphe4CYB7/8k6lJmDaTBNULKCOA==",
"dev": true,
"engines": ">= 18.0.0"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.0.tgz",
@ -764,24 +773,24 @@
}
},
"node_modules/@volar/language-core": {
"version": "2.4.0-alpha.17",
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.0-alpha.17.tgz",
"integrity": "sha512-FF9g89QZUVJpgZvrNpA+v5Sgo7MdUjeA1celxCe4nFTpfp4P/FUdZ1lgeYy7ZS5r13oC4Ei6HqWpfLN7PFM60w==",
"version": "2.4.0-alpha.18",
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.0-alpha.18.tgz",
"integrity": "sha512-JAYeJvYQQROmVRtSBIczaPjP3DX4QW1fOqW1Ebs0d3Y3EwSNRglz03dSv0Dm61dzd0Yx3WgTW3hndDnTQqgmyg==",
"dev": true,
"dependencies": {
"@volar/source-map": "2.4.0-alpha.17"
"@volar/source-map": "2.4.0-alpha.18"
}
},
"node_modules/@volar/language-server": {
"version": "2.4.0-alpha.17",
"resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.0-alpha.17.tgz",
"integrity": "sha512-K+ffVR484Zzq9tTeoRxwtvGzvhR8qCpKhcgsPkCPeCh904yr1zxkMX728fhTePB9nZtKpI0jDuqdQA+338Gl1Q==",
"version": "2.4.0-alpha.18",
"resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.0-alpha.18.tgz",
"integrity": "sha512-dciHEE/R5kzI0bY71QfkoCVQ3cQI6g9MHfA4oIP6UhnJy0CdleUalWSygOXoD3Nq7Yk6wn2BRrb1PP5MsadY/Q==",
"dev": true,
"dependencies": {
"@volar/language-core": "2.4.0-alpha.17",
"@volar/language-service": "2.4.0-alpha.17",
"@volar/snapshot-document": "2.4.0-alpha.17",
"@volar/typescript": "2.4.0-alpha.17",
"@volar/language-core": "2.4.0-alpha.18",
"@volar/language-service": "2.4.0-alpha.18",
"@volar/snapshot-document": "2.4.0-alpha.18",
"@volar/typescript": "2.4.0-alpha.18",
"path-browserify": "^1.0.1",
"request-light": "^0.7.0",
"vscode-languageserver": "^9.0.1",
@ -791,21 +800,21 @@
}
},
"node_modules/@volar/language-service": {
"version": "2.4.0-alpha.17",
"resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.0-alpha.17.tgz",
"integrity": "sha512-rq+O/Nf7krrq611khGOH6+f9c5i7vQiDPXOLuGks2bBWjPUqaN7dR8agMm+9BTlAj0IItArKqUncYr5mYU78kQ==",
"version": "2.4.0-alpha.18",
"resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.0-alpha.18.tgz",
"integrity": "sha512-EuetrtbEtudi9buinWAG5U3Jam5dY27zXd/7GYnx542kBwanWOBM8i4DAQd0z7M11fOxXgybxPA933uaSyaOog==",
"dev": true,
"dependencies": {
"@volar/language-core": "2.4.0-alpha.17",
"@volar/language-core": "2.4.0-alpha.18",
"vscode-languageserver-protocol": "^3.17.5",
"vscode-languageserver-textdocument": "^1.0.11",
"vscode-uri": "^3.0.8"
}
},
"node_modules/@volar/snapshot-document": {
"version": "2.4.0-alpha.17",
"resolved": "https://registry.npmjs.org/@volar/snapshot-document/-/snapshot-document-2.4.0-alpha.17.tgz",
"integrity": "sha512-7h8cf8r+gKU0EEn68pulM1yER1iFshQR/fVT0Bw7T7cbRLe7afnaXbU+jg9yKoEUuJ/B8GU3a/5IBLofY6ZqVg==",
"version": "2.4.0-alpha.18",
"resolved": "https://registry.npmjs.org/@volar/snapshot-document/-/snapshot-document-2.4.0-alpha.18.tgz",
"integrity": "sha512-JAeclEly/wnILhR4Pu9MpgBLInZJH49O1zoy8fU+pk5I+zpv7JIEby5z2UFAS60+sIDnxBdAGd7rZ5VibE70vg==",
"dev": true,
"dependencies": {
"vscode-languageserver-protocol": "^3.17.5",
@ -813,18 +822,18 @@
}
},
"node_modules/@volar/source-map": {
"version": "2.4.0-alpha.17",
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.0-alpha.17.tgz",
"integrity": "sha512-6LOuR2nIloQCSNMNcUPRPLjL5CInIE1pYZ8lifOCSxQRiz8GcWaOm34kAvdm7pzPQqMRHBBnV/Ihkdt/w7oWAQ==",
"version": "2.4.0-alpha.18",
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.0-alpha.18.tgz",
"integrity": "sha512-MTeCV9MUwwsH0sNFiZwKtFrrVZUK6p8ioZs3xFzHc2cvDXHWlYN3bChdQtwKX+FY2HG6H3CfAu1pKijolzIQ8g==",
"dev": true
},
"node_modules/@volar/typescript": {
"version": "2.4.0-alpha.17",
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.0-alpha.17.tgz",
"integrity": "sha512-oJlz5xJd0O1Xe/I7AV3kPpV6gXlcyxfpMcj/w4/wGY5AxFHxyy5i7VhaE/BVk99zsT6M2KxcZyUSsL55RlNXlQ==",
"version": "2.4.0-alpha.18",
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.0-alpha.18.tgz",
"integrity": "sha512-sXh5Y8sqGUkgxpMWUGvRXggxYHAVxg0Pa1C42lQZuPDrW6vHJPR0VCK8Sr7WJsAW530HuNQT/ZIskmXtxjybMQ==",
"dev": true,
"dependencies": {
"@volar/language-core": "2.4.0-alpha.17",
"@volar/language-core": "2.4.0-alpha.18",
"path-browserify": "^1.0.1",
"vscode-uri": "^3.0.8"
}
@ -900,20 +909,36 @@
"@vue/shared": "3.4.33"
}
},
"node_modules/@vue/language-core": {
"version": "2.0.26",
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.26.tgz",
"integrity": "sha512-/lt6SfQ3O1yDAhPsnLv9iSUgXd1dMHqUm/t3RctfqjuwQf1LnftZ414X3UBn6aXT4MiwXWtbNJ4Z0NZWwDWgJQ==",
"node_modules/@vue/compiler-vue2": {
"version": "2.7.16",
"resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz",
"integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==",
"dev": true,
"dependencies": {
"@volar/language-core": "~2.4.0-alpha.15",
"de-indent": "^1.0.2",
"he": "^1.2.0"
}
},
"node_modules/@vue/devtools-api": {
"version": "6.6.3",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.3.tgz",
"integrity": "sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==",
"dev": true
},
"node_modules/@vue/language-core": {
"version": "2.0.29",
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.29.tgz",
"integrity": "sha512-o2qz9JPjhdoVj8D2+9bDXbaI4q2uZTHQA/dbyZT4Bj1FR9viZxDJnLcKVHfxdn6wsOzRgpqIzJEEmSSvgMvDTQ==",
"dev": true,
"dependencies": {
"@volar/language-core": "~2.4.0-alpha.18",
"@vue/compiler-dom": "^3.4.0",
"@vue/compiler-vue2": "^2.7.16",
"@vue/shared": "^3.4.0",
"computeds": "^0.0.1",
"minimatch": "^9.0.3",
"muggle-string": "^0.4.1",
"path-browserify": "^1.0.1",
"vue-template-compiler": "^2.7.14"
"path-browserify": "^1.0.1"
},
"peerDependencies": {
"typescript": "*"
@ -925,16 +950,16 @@
}
},
"node_modules/@vue/language-server": {
"version": "2.0.26",
"resolved": "https://registry.npmjs.org/@vue/language-server/-/language-server-2.0.26.tgz",
"integrity": "sha512-t+kwaHMefRdq55Q/tkGRncOOVkzcfAghR6rKgIyTh1oTzQippEhx42bLVqYvxNjyNx/yvwd7QXNDjyPRLx23kA==",
"version": "2.0.29",
"resolved": "https://registry.npmjs.org/@vue/language-server/-/language-server-2.0.29.tgz",
"integrity": "sha512-Nni7KwxQBzFVKJj9tLIDe1MVmFBFHtup8yC5LIrWq+8/LFNcznf9QHBjgEWEmwfz6PKtv46vH1hqHlmPrClf/w==",
"dev": true,
"dependencies": {
"@volar/language-core": "~2.4.0-alpha.15",
"@volar/language-server": "~2.4.0-alpha.15",
"@vue/language-core": "2.0.26",
"@vue/language-service": "2.0.26",
"@vue/typescript-plugin": "2.0.26",
"@volar/language-core": "~2.4.0-alpha.18",
"@volar/language-server": "~2.4.0-alpha.18",
"@vue/language-core": "2.0.29",
"@vue/language-service": "2.0.29",
"@vue/typescript-plugin": "2.0.29",
"vscode-languageserver-protocol": "^3.17.5",
"vscode-uri": "^3.0.8"
},
@ -943,28 +968,28 @@
}
},
"node_modules/@vue/language-service": {
"version": "2.0.26",
"resolved": "https://registry.npmjs.org/@vue/language-service/-/language-service-2.0.26.tgz",
"integrity": "sha512-Lo4RJ+fcKrF09iIygcLFm3wdTEbmMb+l+/bpA3TXrgZk8+SbOkh6LSexJBvRQfStZSKYIV6FMgJ3ME6qpXpYqA==",
"version": "2.0.29",
"resolved": "https://registry.npmjs.org/@vue/language-service/-/language-service-2.0.29.tgz",
"integrity": "sha512-lY54t7KNp1WKXfYccTj9PizwE8zrswTZbYzYdLyoeLyLwcO/JlkMssTrt1G+64TLBwBptvV9PwvNw5Bp2YxJHg==",
"dev": true,
"dependencies": {
"@volar/language-core": "~2.4.0-alpha.15",
"@volar/language-service": "~2.4.0-alpha.15",
"@volar/typescript": "~2.4.0-alpha.15",
"@volar/language-core": "~2.4.0-alpha.18",
"@volar/language-service": "~2.4.0-alpha.18",
"@volar/typescript": "~2.4.0-alpha.18",
"@vue/compiler-dom": "^3.4.0",
"@vue/language-core": "2.0.26",
"@vue/language-core": "2.0.29",
"@vue/shared": "^3.4.0",
"@vue/typescript-plugin": "2.0.26",
"@vue/typescript-plugin": "2.0.29",
"computeds": "^0.0.1",
"path-browserify": "^1.0.1",
"volar-service-css": "volar-2.4",
"volar-service-emmet": "volar-2.4",
"volar-service-html": "volar-2.4",
"volar-service-json": "volar-2.4",
"volar-service-pug": "volar-2.4",
"volar-service-pug-beautify": "volar-2.4",
"volar-service-typescript": "volar-2.4",
"volar-service-typescript-twoslash-queries": "volar-2.4",
"volar-service-css": "0.0.59",
"volar-service-emmet": "0.0.59",
"volar-service-html": "0.0.59",
"volar-service-json": "0.0.59",
"volar-service-pug": "0.0.59",
"volar-service-pug-beautify": "0.0.59",
"volar-service-typescript": "0.0.59",
"volar-service-typescript-twoslash-queries": "0.0.59",
"vscode-html-languageservice": "^5.2.0",
"vscode-languageserver-textdocument": "^1.0.11",
"vscode-uri": "^3.0.8"
@ -1022,13 +1047,13 @@
"dev": true
},
"node_modules/@vue/typescript-plugin": {
"version": "2.0.26",
"resolved": "https://registry.npmjs.org/@vue/typescript-plugin/-/typescript-plugin-2.0.26.tgz",
"integrity": "sha512-C0F2lpv1m9LO1sEIJmZEN7tSzRwPObbYHtxftDlrvUKNWuEu4OqilnRuUCNyAQRq7UrkNR3fv1Dc+OcKOj0dEg==",
"version": "2.0.29",
"resolved": "https://registry.npmjs.org/@vue/typescript-plugin/-/typescript-plugin-2.0.29.tgz",
"integrity": "sha512-cO/cP467bGONkm/imEVvcRg77/VmoWpLyO94jSwLAt8QV0X9l414SwsRdsae+wGMPV+6k7rweer0SP16A0HYdw==",
"dev": true,
"dependencies": {
"@volar/typescript": "~2.4.0-alpha.15",
"@vue/language-core": "2.0.26",
"@volar/typescript": "~2.4.0-alpha.18",
"@vue/language-core": "2.0.29",
"@vue/shared": "^3.4.0"
}
},
@ -2529,9 +2554,9 @@
}
},
"node_modules/vscode-languageserver-textdocument": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz",
"integrity": "sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==",
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz",
"integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==",
"dev": true
},
"node_modules/vscode-languageserver-types": {
@ -2572,24 +2597,29 @@
}
}
},
"node_modules/vue-template-compiler": {
"version": "2.7.16",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
"integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==",
"node_modules/vue-router": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.1.tgz",
"integrity": "sha512-njTLt/6gYGgIhv+U8nc5J6JpJpntFgy4fptRJ9Dp2qWQRo/PekB5DbKRYRPt0kM6feXysPKl7A5BjOmOJL5Ttw==",
"dev": true,
"dependencies": {
"de-indent": "^1.0.2",
"he": "^1.2.0"
"@vue/devtools-api": "^6.6.3"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/vue-tsc": {
"version": "2.0.26",
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.26.tgz",
"integrity": "sha512-tOhuwy2bIXbMhz82ef37qeiaQHMXKQkD6mOF6CCPl3/uYtST3l6fdNyfMxipudrQTxTfXVPlgJdMENBFfC1CfQ==",
"version": "2.0.29",
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.29.tgz",
"integrity": "sha512-MHhsfyxO3mYShZCGYNziSbc63x7cQ5g9kvijV7dRe1TTXBRLxXyL0FnXWpUF1xII2mJ86mwYpYsUmMwkmerq7Q==",
"dev": true,
"dependencies": {
"@volar/typescript": "~2.4.0-alpha.15",
"@vue/language-core": "2.0.26",
"@volar/typescript": "~2.4.0-alpha.18",
"@vue/language-core": "2.0.29",
"semver": "^7.5.4"
},
"bin": {

View File

@ -14,6 +14,7 @@
"vue": "^3.4.29"
},
"devDependencies": {
"@rachelambda/well-rested": "^0.1.3",
"@tsconfig/node20": "^20.1.4",
"@types/node": "^20.14.5",
"@vitejs/plugin-vue": "^5.0.5",
@ -26,6 +27,7 @@
"typescript-language-server": "^4.3.3",
"vite": "^5.3.1",
"vite-plugin-pages": "^0.32.3",
"vue-router": "^4.4.1",
"vue-tsc": "^2.0.21"
}
}

21
front/src/Api.ts Normal file
View File

@ -0,0 +1,21 @@
import { API } from '@rachelambda/well-rested'
type PlantAPI = {
"/api/echo": {
kind: "POST"
request: string
response: string
}
"/api/get_counter": {
kind: "POST"
response: number
}
"/api/set_counter": {
kind: "POST"
request: "add" | "sub"
response: number
}
}
export const api = new API<PlantAPI>('http://localhost:3013')
export default api

View File

@ -1,76 +0,0 @@
export interface QueryParams {
[key: string]: (string | number)
}
export interface APIDef {
[key: string]: ({
kind: "POST",
query?: QueryParams,
request?: Object,
response: Object,
} | {
kind: "GET",
query?: QueryParams,
response: Object,
})
}
type A = {
"/api/blah": {
kind: "GET",
request: {
mjau: number
cat: string
}
response: {
status: number
}
}
}
export type ValidEndPointForMethod<T, U extends (keyof T & string), method extends "GET" | "POST">
= T[U] extends { kind: method } ? U : never
export type QR = { query: Object, request: Object }
export class API<T extends APIDef> {
base: string;
constructor(base: string) {
this.base = base;
}
private dispatch(method : "POST" | "GET",
endpoint : keyof T & string,
req : T[typeof endpoint] & QR):
Promise<T[typeof endpoint]["response"]> {
let url : URL = new URL(endpoint, this.base)
let opts : RequestInit = { method }
if (req.query != null) {
const params = new URLSearchParams()
for (const [key, value] of Object.entries(req.query)) {
params.set(key, typeof(value) == "string" ? value : value.toString())
}
url = new URL(params.toString(), url)
}
if (method == "POST" && req.request != null) {
opts.body = JSON.stringify(req.request)
opts.headers = { "Content-Type": "application/json" }
}
return fetch(url, opts)
}
get(endpoint : ValidEndPointForMethod<T, string, "GET">,
req: T[typeof endpoint] & QR):
Promise<T[typeof endpoint]["response"]> {
return this.dispatch("GET", endpoint, req)
}
post(endpoint : ValidEndPointForMethod<T, string, "POST">,
req: T[typeof endpoint] & QR):
Promise<T[typeof endpoint]["response"]> {
return this.dispatch("POST", endpoint, req)
}
}
const test = new API<A>("mjau")

View File

@ -1,13 +1,18 @@
import './css/main.scss'
import { createMemoryHistory, createRouter } from 'vue-router'
import { createApp } from 'vue'
import routes from '~pages'
import { api } from './Api.js'
import App from './App.vue'
const app = createApp(
App,
{ routes },
)
const router = createRouter({
history: createMemoryHistory(),
routes
})
const app = createApp(App)
app.use(router)
app.mount('#app')

View File

@ -1,3 +1,61 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { api } from '../Api.js'
let content = ref<string>("")
let counter = ref<number>()
onMounted(async () => {
const response : string | Response = await api.post("/api/echo", {
request: "hii world !! ^-^"
})
if (typeof(response) == "string") {
content.value = response
} else {
content.value = response.statusText
}
await get_counter()
})
async function get_counter() {
const response : number | Response = await api.post("/api/get_counter", {})
if (typeof(response) == "number") {
counter.value = response
} else {
console.log(response)
}
}
async function set_counter(action : "add" | "sub") {
const response : number | Response = await api.post("/api/set_counter", {
request: action
})
if (typeof(response) == "number") {
counter.value = response
} else {
console.log(response)
}
}
async function add() {
await set_counter("add");
}
async function sub() {
await set_counter("sub");
}
</script>
<template>
<h1>hii world !! ^-^</h1>
<h1>index :3</h1>
<p>{{ content }}</p>
<pre><code>{{ counter }}</code></pre>
<button @click="add">add</button>
<button @click="sub">sub</button>
</template>