feat!: use msgpack instead of json (#40)

Reviewed-on: #40
Co-authored-by: Felipe Contreras Salinas <felipe@bstr.cl>
Co-committed-by: Felipe Contreras Salinas <felipe@bstr.cl>
This commit is contained in:
Felipe 2024-03-06 21:38:04 -03:00 committed by Felipe
parent 7f62b1a22d
commit 1bffd10585
Signed by: Ludwig
GPG key ID: 441A26F83D31FAFF
9 changed files with 527 additions and 511 deletions

763
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,18 +5,17 @@ edition = "2021"
license = "AGPL-3.0" license = "AGPL-3.0"
[dependencies] [dependencies]
anyhow = "1.0.79" anyhow = "1.0.80"
axum = { version = "0.7.4", default-features = false, features = [ axum = { version = "0.7.4", default-features = false, features = [
"json",
"tracing", "tracing",
"tokio", "tokio",
"http1", "http1",
"http2", "http2",
] } ] }
axum-msgpack = "0.4.0"
dotenvy = "0.15.7" dotenvy = "0.15.7"
futures = { version = "0.3.30", default-features = false } futures = { version = "0.3.30", default-features = false }
serde = { version = "1.0.195", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.111"
sqlx = { version = "0.7.3", default-features = false, features = [ sqlx = { version = "0.7.3", default-features = false, features = [
"macros", "macros",
"migrate", "migrate",
@ -24,12 +23,12 @@ sqlx = { version = "0.7.3", default-features = false, features = [
"sqlite", "sqlite",
"tls-rustls", "tls-rustls",
] } ] }
tokio = { version = "1.35.1", default-features = false, features = [ tokio = { version = "1.36.0", default-features = false, features = [
"macros", "macros",
"rt-multi-thread", "rt-multi-thread",
"signal", "signal",
] } ] }
tower-http = { version = "0.5.1", default-features = false, features = ["fs"] } tower-http = { version = "0.5.2", default-features = false, features = ["fs"] }
tracing = "0.1.40" tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", default-features = false, features = [ tracing-subscriber = { version = "0.3.18", default-features = false, features = [
"env-filter", "env-filter",
@ -39,4 +38,4 @@ tracing-subscriber = { version = "0.3.18", default-features = false, features =
] } ] }
[dev-dependencies] [dev-dependencies]
axum-test = "14.2.2" axum-test = { version = "14.4.0", features = ["msgpack"] }

View file

@ -1,5 +1,5 @@
##### Builder #### ##### Builder ####
FROM rust:1.75-alpine3.19 as builder FROM rust:1.76-alpine3.19 as builder
# Install dependencies # Install dependencies
RUN apk add --no-cache sqlite npm musl-dev fd minify && npm install -g typescript RUN apk add --no-cache sqlite npm musl-dev fd minify && npm install -g typescript
@ -37,7 +37,7 @@ WORKDIR /usr/src/huellas/ts-client/
RUN npm ci RUN npm ci
# Transpile and delete the first line of javascript ts-client # Transpile and delete the first line of javascript ts-client
RUN tsc && sed -i '1d' build/client.js RUN tsc && sed -i '1,2d' build/client.js
# Minify static files # Minify static files
COPY static /usr/src/huellas/static/ COPY static /usr/src/huellas/static/

View file

@ -1,7 +1,8 @@
use axum::extract::{Json, Path, State}; use axum::extract::{Path, State};
use axum::http::StatusCode; use axum::http::StatusCode;
use axum::routing::{delete, get}; use axum::routing::{delete, get};
use axum::Router; use axum::Router;
use axum_msgpack::MsgPack;
use futures::TryStreamExt; use futures::TryStreamExt;
use sqlx::sqlite::SqlitePool; use sqlx::sqlite::SqlitePool;
@ -15,7 +16,7 @@ where
(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) (StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
} }
async fn get_places(State(pool): State<SqlitePool>) -> Result<Json<Vec<Place>>> { async fn get_places(State(pool): State<SqlitePool>) -> Result<MsgPack<Vec<Place>>> {
let places = sqlx::query!( let places = sqlx::query!(
r#"SELECT id, name, address, open_hours, icon, description, url, r#"SELECT id, name, address, open_hours, icon, description, url,
longitude as "longitude: f64", latitude as "latitude: f64" longitude as "longitude: f64", latitude as "latitude: f64"
@ -38,13 +39,13 @@ async fn get_places(State(pool): State<SqlitePool>) -> Result<Json<Vec<Place>>>
.await .await
.map_err(internal_error)?; .map_err(internal_error)?;
Ok(Json(places)) Ok(MsgPack(places))
} }
async fn upsert_place( async fn upsert_place(
State(pool): State<SqlitePool>, State(pool): State<SqlitePool>,
Json(place): Json<Place>, MsgPack(place): MsgPack<Place>,
) -> Result<Json<Place>> { ) -> Result<MsgPack<Place>> {
if place.id.is_some() { if place.id.is_some() {
update_place(pool, place).await update_place(pool, place).await
} else { } else {
@ -52,7 +53,7 @@ async fn upsert_place(
} }
} }
async fn insert_place(pool: SqlitePool, mut place: Place) -> Result<Json<Place>> { async fn insert_place(pool: SqlitePool, mut place: Place) -> Result<MsgPack<Place>> {
let id = sqlx::query_scalar!( let id = sqlx::query_scalar!(
r#"INSERT INTO places r#"INSERT INTO places
(name, address, open_hours, icon, description, longitude, latitude, url) (name, address, open_hours, icon, description, longitude, latitude, url)
@ -72,10 +73,10 @@ async fn insert_place(pool: SqlitePool, mut place: Place) -> Result<Json<Place>>
.map_err(internal_error)?; .map_err(internal_error)?;
place.id = Some(id); place.id = Some(id);
Ok(Json(place)) Ok(MsgPack(place))
} }
async fn update_place(pool: SqlitePool, place: Place) -> Result<Json<Place>> { async fn update_place(pool: SqlitePool, place: Place) -> Result<MsgPack<Place>> {
let result = sqlx::query!( let result = sqlx::query!(
r#"UPDATE places r#"UPDATE places
SET (name, address, open_hours, icon, description, longitude, latitude, url) SET (name, address, open_hours, icon, description, longitude, latitude, url)
@ -96,7 +97,7 @@ async fn update_place(pool: SqlitePool, place: Place) -> Result<Json<Place>> {
.map_err(internal_error)?; .map_err(internal_error)?;
if result.rows_affected() == 1 { if result.rows_affected() == 1 {
Ok(Json(place)) Ok(MsgPack(place))
} else { } else {
Err((StatusCode::NOT_FOUND, "".to_owned())) Err((StatusCode::NOT_FOUND, "".to_owned()))
} }
@ -167,10 +168,10 @@ mod tests {
url: Some("https://www.sherlock-holmes.co.uk/".to_owned()), url: Some("https://www.sherlock-holmes.co.uk/".to_owned()),
}; };
// Insert the place // Insert the place
let res = server.put("/places").json(&place).await; let res = server.put("/places").msgpack(&place).await;
// We should get a success on the request // We should get a success on the request
assert_eq!(res.status_code(), StatusCode::OK); assert_eq!(res.status_code(), StatusCode::OK);
let res_place: Place = res.json(); let res_place: Place = res.msgpack();
// The inserted place should have an ID // The inserted place should have an ID
assert!(res_place.id.is_some()); assert!(res_place.id.is_some());
// Add the returned ID to the original place // Add the returned ID to the original place
@ -212,10 +213,10 @@ mod tests {
]; ];
// insert the places // insert the places
for p in &mut places { for p in &mut places {
let res = server.put("/places").json(&p).await; let res = server.put("/places").msgpack(&p).await;
// We should get a success on the request // We should get a success on the request
assert_eq!(res.status_code(), StatusCode::OK); assert_eq!(res.status_code(), StatusCode::OK);
let res_place: Place = res.json(); let res_place: Place = res.msgpack();
// The inserted place should have an ID // The inserted place should have an ID
assert!(res_place.id.is_some()); assert!(res_place.id.is_some());
// Add the returned ID to the original place // Add the returned ID to the original place
@ -225,7 +226,7 @@ mod tests {
let res = server.get("/places").await; let res = server.get("/places").await;
// We should get a success on the request // We should get a success on the request
assert_eq!(res.status_code(), StatusCode::OK); assert_eq!(res.status_code(), StatusCode::OK);
let mut res_places: Vec<Place> = res.json(); let mut res_places: Vec<Place> = res.msgpack();
// and they should be equal // and they should be equal
places.sort_by(|a, b| a.id.cmp(&b.id)); places.sort_by(|a, b| a.id.cmp(&b.id));
res_places.sort_by(|a, b| a.id.cmp(&b.id)); res_places.sort_by(|a, b| a.id.cmp(&b.id));
@ -262,10 +263,10 @@ mod tests {
]; ];
// insert the places // insert the places
for p in &mut places { for p in &mut places {
let res = server.put("/places").json(&p).await; let res = server.put("/places").msgpack(&p).await;
// We should get a success on the request // We should get a success on the request
assert_eq!(res.status_code(), StatusCode::OK); assert_eq!(res.status_code(), StatusCode::OK);
let res_place: Place = res.json(); let res_place: Place = res.msgpack();
// The inserted place should have an ID // The inserted place should have an ID
assert!(res_place.id.is_some()); assert!(res_place.id.is_some());
// Add the returned ID to the original place // Add the returned ID to the original place
@ -282,7 +283,7 @@ mod tests {
let res = server.get("/places").await; let res = server.get("/places").await;
// We should get a success on the request // We should get a success on the request
assert_eq!(res.status_code(), StatusCode::OK); assert_eq!(res.status_code(), StatusCode::OK);
let res_places: Vec<Place> = res.json(); let res_places: Vec<Place> = res.msgpack();
// we should only get the second place // we should only get the second place
assert_eq!(&places[1..], res_places.as_slice()); assert_eq!(&places[1..], res_places.as_slice());
Ok(()) Ok(())
@ -326,16 +327,16 @@ mod tests {
}, },
]; ];
// insert original place // insert original place
let res = server.put("/places").json(&places[0]).await; let res = server.put("/places").msgpack(&places[0]).await;
// We should get a success on the request // We should get a success on the request
assert_eq!(res.status_code(), StatusCode::OK); assert_eq!(res.status_code(), StatusCode::OK);
let res_place: Place = res.json(); let res_place: Place = res.msgpack();
// The inserted place should have an ID // The inserted place should have an ID
assert!(res_place.id.is_some()); assert!(res_place.id.is_some());
// Add the returned ID to the new place so we can do the update // Add the returned ID to the new place so we can do the update
places[1].id = res_place.id; places[1].id = res_place.id;
// update the place // update the place
let res = server.put("/places").json(&places[1]).await; let res = server.put("/places").msgpack(&places[1]).await;
// We should get a success on the request // We should get a success on the request
assert_eq!(res.status_code(), StatusCode::OK); assert_eq!(res.status_code(), StatusCode::OK);
@ -343,7 +344,7 @@ mod tests {
let res = server.get("/places").await; let res = server.get("/places").await;
// We should get a success on the request // We should get a success on the request
assert_eq!(res.status_code(), StatusCode::OK); assert_eq!(res.status_code(), StatusCode::OK);
let res_places: Vec<Place> = res.json(); let res_places: Vec<Place> = res.msgpack();
// we should get the updated place // we should get the updated place
assert_eq!(&places[1..], res_places.as_slice()); assert_eq!(&places[1..], res_places.as_slice());
Ok(()) Ok(())

View file

@ -17,6 +17,9 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-contextmenu/1.4.0/leaflet.contextmenu.min.js" <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-contextmenu/1.4.0/leaflet.contextmenu.min.js"
integrity="sha512-8sfQf8cr0KjCeN32YPfjvLU2cMvyY1lhCXTMfpTZ16CvwIzeVQtwtKlxeSqFs/TpXjKhp1Dcv77LQmn1VFaOZg==" integrity="sha512-8sfQf8cr0KjCeN32YPfjvLU2cMvyY1lhCXTMfpTZ16CvwIzeVQtwtKlxeSqFs/TpXjKhp1Dcv77LQmn1VFaOZg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script> crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script crossorigin src="https://unpkg.com/@msgpack/msgpack"
integrity="sha512-t/LymXW9iw9p0bciQ6uASEj+8XE/p+07CCmCyNwn06F4FK2s80IuHCY62bfSorcD4ktOJavXApp5rqtIVlg3+g=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<style type="text/css" media="screen"> <style type="text/css" media="screen">
body { body {

View file

@ -1,3 +1,3 @@
build/client.js: client.ts build/client.js: client.ts
tsc tsc
sed -i '1d' build/client.js sed -i '1,2d' build/client.js

View file

@ -1,5 +1,6 @@
import * as L from 'leaflet-contextmenu'; import * as L from 'leaflet-contextmenu';
import { Feature, FeatureCollection, Point } from 'geojson'; import { Feature, FeatureCollection, Point } from 'geojson';
import * as MessagePack from "@msgpack/msgpack";
interface PlaceModel { interface PlaceModel {
id: number | null; id: number | null;
@ -14,7 +15,8 @@ interface PlaceModel {
} }
async function loadPlaces(): Promise<Array<PlaceModel>> { async function loadPlaces(): Promise<Array<PlaceModel>> {
return await fetch('places').then(response => response.json()); let bytes = await fetch('places').then(response => response.body);
return (await MessagePack.decodeAsync(bytes)) as Array<PlaceModel>;
} }
function toFeature(place: PlaceModel): Feature { function toFeature(place: PlaceModel): Feature {
@ -120,11 +122,12 @@ async function createPlace(): Promise<void> {
await fetch('places', { await fetch('places', {
method: 'PUT', method: 'PUT',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/msgpack',
}, },
body: JSON.stringify(newPlace), body: MessagePack.encode(newPlace),
}) })
.then((response) => response.json()) .then((response) => response.body)
.then((bytes) => MessagePack.decodeAsync(bytes))
.then((place: PlaceModel) => { .then((place: PlaceModel) => {
places.set( places.set(
toStr({ lat: place.latitude, lng: place.longitude }), toStr({ lat: place.latitude, lng: place.longitude }),
@ -143,11 +146,12 @@ async function editPlace(): Promise<void> {
await fetch('places', { await fetch('places', {
method: 'PUT', method: 'PUT',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/msgpack',
}, },
body: JSON.stringify(newPlace), body: MessagePack.encode(newPlace),
}) })
.then((response) => response.json()) .then((response) => response.body)
.then((bytes) => MessagePack.decodeAsync(bytes))
.then((place: PlaceModel) => { .then((place: PlaceModel) => {
places.set( places.set(
toStr({ lat: place.latitude, lng: place.longitude }), toStr({ lat: place.latitude, lng: place.longitude }),
@ -183,6 +187,7 @@ function toLink(url: string): string {
} }
return `<a href="${url}" target="_blank">${content}</a>` return `<a href="${url}" target="_blank">${content}</a>`
} }
async function setupMap(): Promise<void> { async function setupMap(): Promise<void> {
/* Create/Edit form */ /* Create/Edit form */
const modal = document.getElementById("modal"); const modal = document.getElementById("modal");

View file

@ -5,12 +5,21 @@
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"@msgpack/msgpack": "^3.0.0-beta2",
"geojson": "^0.5.0", "geojson": "^0.5.0",
"leaflet": "^1.8.0", "leaflet": "^1.9.4",
"leaflet-contextmenu": "^1.4.0" "leaflet-contextmenu": "^1.4.0"
}, },
"devDependencies": { "devDependencies": {
"@types/leaflet": "^1.7.11" "@types/leaflet": "^1.9.8"
}
},
"node_modules/@msgpack/msgpack": {
"version": "3.0.0-beta2",
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.0.0-beta2.tgz",
"integrity": "sha512-y+l1PNV0XDyY8sM3YtuMLK5vE3/hkfId+Do8pLo/OPxfxuFAUwcGz3oiiUuV46/aBpwTzZ+mRWVMtlSKbradhw==",
"engines": {
"node": ">= 14"
} }
}, },
"node_modules/@types/geojson": { "node_modules/@types/geojson": {
@ -20,9 +29,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/leaflet": { "node_modules/@types/leaflet": {
"version": "1.7.11", "version": "1.9.8",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.7.11.tgz", "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.8.tgz",
"integrity": "sha512-VwAYom2pfIAf/pLj1VR5aLltd4tOtHyvfaJlNYCoejzP2nu52PrMi1ehsLRMUS+bgafmIIKBV1cMfKeS+uJ0Vg==", "integrity": "sha512-EXdsL4EhoUtGm2GC2ZYtXn+Fzc6pluVgagvo2VC1RHWToLGlTRwVYoDpqS/7QXa01rmDyBjJk3Catpf60VMkwg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/geojson": "*" "@types/geojson": "*"
@ -37,9 +46,9 @@
} }
}, },
"node_modules/leaflet": { "node_modules/leaflet": {
"version": "1.8.0", "version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.8.0.tgz", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA==" "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
}, },
"node_modules/leaflet-contextmenu": { "node_modules/leaflet-contextmenu": {
"version": "1.4.0", "version": "1.4.0",
@ -48,6 +57,11 @@
} }
}, },
"dependencies": { "dependencies": {
"@msgpack/msgpack": {
"version": "3.0.0-beta2",
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.0.0-beta2.tgz",
"integrity": "sha512-y+l1PNV0XDyY8sM3YtuMLK5vE3/hkfId+Do8pLo/OPxfxuFAUwcGz3oiiUuV46/aBpwTzZ+mRWVMtlSKbradhw=="
},
"@types/geojson": { "@types/geojson": {
"version": "7946.0.10", "version": "7946.0.10",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
@ -55,9 +69,9 @@
"dev": true "dev": true
}, },
"@types/leaflet": { "@types/leaflet": {
"version": "1.7.11", "version": "1.9.8",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.7.11.tgz", "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.8.tgz",
"integrity": "sha512-VwAYom2pfIAf/pLj1VR5aLltd4tOtHyvfaJlNYCoejzP2nu52PrMi1ehsLRMUS+bgafmIIKBV1cMfKeS+uJ0Vg==", "integrity": "sha512-EXdsL4EhoUtGm2GC2ZYtXn+Fzc6pluVgagvo2VC1RHWToLGlTRwVYoDpqS/7QXa01rmDyBjJk3Catpf60VMkwg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/geojson": "*" "@types/geojson": "*"
@ -69,9 +83,9 @@
"integrity": "sha512-/Bx5lEn+qRF4TfQ5aLu6NH+UKtvIv7Lhc487y/c8BdludrCTpiWf9wyI0RTyqg49MFefIAvFDuEi5Dfd/zgNxQ==" "integrity": "sha512-/Bx5lEn+qRF4TfQ5aLu6NH+UKtvIv7Lhc487y/c8BdludrCTpiWf9wyI0RTyqg49MFefIAvFDuEi5Dfd/zgNxQ=="
}, },
"leaflet": { "leaflet": {
"version": "1.8.0", "version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.8.0.tgz", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA==" "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
}, },
"leaflet-contextmenu": { "leaflet-contextmenu": {
"version": "1.4.0", "version": "1.4.0",

View file

@ -1,10 +1,11 @@
{ {
"devDependencies": { "devDependencies": {
"@types/leaflet": "^1.7.11" "@types/leaflet": "^1.9.8"
}, },
"dependencies": { "dependencies": {
"@msgpack/msgpack": "^3.0.0-beta2",
"geojson": "^0.5.0", "geojson": "^0.5.0",
"leaflet": "^1.8.0", "leaflet": "^1.9.4",
"leaflet-contextmenu": "^1.4.0" "leaflet-contextmenu": "^1.4.0"
} }
} }